使用Spring Framework 时常犯的十大错误(小结)

1. 错误一:太过关注底层

我们正在解决这个常见错误,是因为 “非我所创” 综合症在软件开发领域很是常见。症状包括经常重写一些常见的代码,很多开发人员都有这种症状。

虽然理解特定库的内部结构及其实现,在很大程度上是好的并且很有必要的(也可以是一个很好的学习过程),但作为软件工程师,不断地处理相同的底层实现细节对个人的开发生涯是有害的。像 Spring 这种抽象框架的存在是有原因的,它将你从重复地手工劳作中解放出来,并允许你专注于更高层次的细节 —— 领域对象和业务逻辑。

因此,接受抽象。下次面对特定问题时,首先进行快速搜索,确定解决该问题的库是否已被集成到 Spring 中;现在,你可能找到一个合适的现成解决方案。比如,一个很有用的库,在本文的其他部分,我将在示例中使用 Project Lombok 注解。Lombok 被用作样板代码生成器,希望懒惰的开发人员在熟悉这个库时不会遇到问题。举个例子,看看使用 Lombok 的 “标准 Java Bean” 是什么样子的:

@Getter
@Setter
@NoArgsConstructor
public class Bean implements Serializable {
  int firstBeanProperty;
  String secondBeanProperty;
}

如你所想,上述代码被编译为:

public class Bean implements Serializable {
  private int firstBeanProperty;
  private String secondBeanProperty;

  public int getFirstBeanProperty() {
    return this.firstBeanProperty;
  }

  public String getSecondBeanProperty() {
    return this.secondBeanProperty;
  }

  public void setFirstBeanProperty(int firstBeanProperty) {
    this.firstBeanProperty = firstBeanProperty;
  }

  public void setSecondBeanProperty(String secondBeanProperty) {
    this.secondBeanProperty = secondBeanProperty;
  }

  public Bean() {
  }
}

但是,请注意,如果你打算在 IDE 中使用 Lombok,很可能需要安装一个插件,可在 此处 找到 Intellij IDEA 版本的插件。

2. 错误二:内部结构 “泄露”

公开你的内部结构,从来都不是一个好主意,因为它在服务设计中造成了不灵活性,从而促进了不好的编码实践。“泄露” 的内部机制表现为使数据库结构可以从某些 API 端点访问。例如,下面的 POJO(“Plain Old Java Object”)类表示数据库中的一个表:

@Entity
@NoArgsConstructor
@Getter
public class TopTalentEntity {

  @Id
  @GeneratedValue
  private Integer id;

  @Column
  private String name;

  public TopTalentEntity(String name) {
    this.name = name;
  }

}

假设,存在一个端点,他需要访问 TopTalentEntity 数据。返回 TopTalentEntity 实例可能很诱人,但更灵活的解决方案是创建一个新的类来表示 API 端点上的 TopTalentEntity 数据。

@AllArgsConstructor
@NoArgsConstructor
@Getter
public class TopTalentData {
  private String name;
}

这样,对数据库后端进行更改将不需要在服务层进行任何额外的更改。考虑下,在 TopTalentEntity 中添加一个 “password” 字段来存储数据库中用户密码的 Hash 值 —— 如果没有 TopTalentData 之类的连接器,忘记更改服务前端,将会意外地暴露一些不必要的秘密信息。

3. 错误三:缺乏关注点分离

随着程序规模的增长,逐渐地,代码组织成为一个越来越重要的问题。讽刺的是,大多数好的软件工程原则开始在规模上崩溃 —— 特别是在没有太多考虑程序体系结构设计的情况下。开发人员最常犯的一个错误就是混淆代码关注点,这很容易做到!

通常,打破 关注点分离 的是将新功能简单地 “倒” 在现有类中。当然,这是一个很好的短期解决方案(对于初学者来说,它需要更少的输入),但它也不可避免地会在将来成为一个问题,无论是在测试期间、维护期间还是介于两者之间。考虑下下面的控制器,它将从数据库返回 TopTalentData。

@RestController
public class TopTalentController {

  private final TopTalentRepository topTalentRepository;

  @RequestMapping("/toptal/get")
  public List<TopTalentData> getTopTalent() {
    return topTalentRepository.findAll()
        .stream()
        .map(this::entityToData)
        .collect(Collectors.toList());
  }

  private TopTalentData entityToData(TopTalentEntity topTalentEntity) {
    return new TopTalentData(topTalentEntity.getName());
  }

}

起初,这段代码似乎没什么特别的问题;它提供了一个从 TopTalentEntity 实例检索出来的 TopTalentData 的 List。然而,仔细观察下,我们可以看到 TopTalentController 实际上在此做了些事情;也就是说,它将请求映射到特定端点,从数据库检索数据,并将从 TopTalentRepository 接收的实体转换为另一种格式。一个“更干净” 的解决方案是将这些关注点分离到他们自己的类中。看起来可能是这个样子的:

@RestController
@RequestMapping("/toptal")
@AllArgsConstructor
public class TopTalentController {

  private final TopTalentService topTalentService;

  @RequestMapping("/get")
  public List<TopTalentData> getTopTalent() {
    return topTalentService.getTopTalent();
  }
}

@AllArgsConstructor
@Service
public class TopTalentService {

  private final TopTalentRepository topTalentRepository;
  private final TopTalentEntityConverter topTalentEntityConverter;

  public List<TopTalentData> getTopTalent() {
    return topTalentRepository.findAll()
        .stream()
        .map(topTalentEntityConverter::toResponse)
        .collect(Collectors.toList());
  }
}

@Component
public class TopTalentEntityConverter {
  public TopTalentData toResponse(TopTalentEntity topTalentEntity) {
    return new TopTalentData(topTalentEntity.getName());
  }
}

这种层次结构的另一个优点是,它允许我们通过检查类名来确定将功能驻留在何处。此外,在测试期间,如果需要,我们可以很容易地用模拟实现来替换任何类。

4. 错误四:缺乏异常处理或处理不当

一致性的主题并非是 Spring(或 Java)所独有的,但仍然是处理 Spring 项目时需要考虑的一个重要方面。虽然编码风格可能存在争议(通常团队或整个公司内部已达成一致),但拥有一个共同的标准最终会极大地提高生产力。对多人团队尤为如此;一致性允许交流发生,而不需要花费很多资源在手把手交接上,也不需要就不同类的职责提供冗长的解释。

考虑一个包含各种配置文件、服务和控制器的 Spring 项目。在命名时保持语义上的一致性,可以创建一个易于搜索的结构,任何新的开发人员都可以按照自己的方式管理代码;例如,将 Config 后缀添加到配置类,服务层以 Service 结尾,以及控制器用 Controller 结尾。

与一致性主题密切相关,服务器端的错误处理值得特别强调。如果你曾经不得不处理编写很差的 API 的异常响应,那你可能知道原因 —— 正确解析异常会是一件痛苦的事情,而确定这些异常最初发生的原因则更为痛苦。

作为一名 API 开发者,理想情况下你希望覆盖所有面向用户的端点,并将他们转换为常见的错误格式。这通常意味着有一个通用的错误代码和描述,而不是逃避解决问题:a) 返回一个 “500 Internal Server Error”信息。b) 直接返回异常的堆栈信息给用户。(实际上,这些都应该不惜一切代价地去避免,因为除了客户端难以处理以外,它还暴露了你的内部信息)。

例如,常见错误响应格式可能长这样:

@Value
public class ErrorResponse {

  private Integer errorCode;
  private String errorMessage;

}

与此类似的事情在大多数流行的 API 中也经常遇到,由于可以容易且系统地记录,效果往往很不错。将异常转换为这种格式可以通过向方法提供 @ExceptionHandler 注解来完成(注解案例可见于第六章)。

5. 错误五:多线程处理不当

不管是桌面应用还是 Web 应用,无论是 Spring 还是 No Spring,多线程都是很难破解的。由并行执行程序所引起的问题是令人毛骨悚然且难以捉摸的,而且常常难以调试 —— 实际上,由于问题的本质,一旦你意识到你正在处理一个并行执行问题,你可能就不得不完全放弃调试器了,并 “手动” 检查代码,直到找到根本上的错误原因。不幸的是,这类问题并没有千篇一律的解决方案;根据具体场景来评估情况,然后从你认为最好的角度来解决问题。

当然,理想情况下,你也希望完全避免多线程错误。同样,不存在那种一刀切的方法,但这有一些调试和防止多线程错误的实际考虑因素:

5.1. 避免全局状态

首先,牢记 “全局状态” 问题。如果你正创建一个多线程应用,那么应该密切关注任何可能全局修改的内容,如果可能的话,将他们全部删掉。如果某个全局变量有必须保持可修改的原因,请仔细使用 synchronization,并对程序性能进行跟踪,以确定没有因为新引入的等待时间而导致系统性能降低。

5.2. 避免可变性

这点直接来自于 函数式编程,并且适用于 OOP,声明应该避免类和状态的改变。简而言之,这意味着放弃 setter 方法,并在所有模型类上拥有私有的 final 字段。它们的值唯一发生变化的时间是在构造期间。这样,你可以确定不会出现争用问题,且访问对象属性将始终提供正确的值。

5.3. 记录关键数据

评估你的程序可能会在何处发生异常,并预先记录所有关键数据。如果发生错误,你将很高兴可以得到信息说明收到了哪些请求,并可更好地了解你的应用程序为什么会出现错误。需要再次注意的是,日志记录引入了额外的文件 I/O,可能会严重影响应用的性能,因此请不要滥用日志。

5.4. 复用现存实现

每当你需要创建自己的线程时(例如:向不同的服务发出异步请求),复用现有的安全实现来代替创建自己的解决方案。这在很大程度上意味着要使用 ExecutorServices 和 Java 8 简洁的函数式 CompletableFutures 来创建线程。Spring 还允许通过 DeferredResult 类来进行异步请求处理。

6. 错误六:不使用基于注解的验证

假设我们之前的 TopTalent 服务需要一个端点来添加新的 TopTalent。此外,假设基于某些原因,每个新名词都需要为 10 个字符长度。执行此操作的一种方法可能如下:

@RequestMapping("/put")
public void addTopTalent(@RequestBody TopTalentData topTalentData) {
  boolean nameNonExistentOrHasInvalidLength =
      Optional.ofNullable(topTalentData)
     .map(TopTalentData::getName)
  .map(name -> name.length() == 10)
  .orElse(true);

  if (nameNonExistentOrInvalidLength) {
    // throw some exception
  }

  topTalentService.addTopTalent(topTalentData);
}

然而,上面的方法(除了构造很差以外)并不是一个真正 “干净” 的解决办法。我们正检查不止一种类型的有效性(即 TopTalentData 不得为空,TopTalentData.name 不得为空,且 TopTalentData.name 为 10 个字符长度),以及在数据无效时抛出异常。

通过在 Spring 中集成 Hibernate validator,数据校验可以更干净地进行。让我们首先重构 addTopTalent 方法来支持验证:

@RequestMapping("/put")
public void addTopTalent(@Valid @NotNull @RequestBody TopTalentData topTalentData) {
  topTalentService.addTopTalent(topTalentData);
}

@ExceptionHandler
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleInvalidTopTalentDataException(MethodArgumentNotValidException methodArgumentNotValidException) {
  // handle validation exception
}

此外,我们还必须指出我们想要在 TopTalentData 类中验证什么属性:

public class TopTalentData {
  @Length(min = 10, max = 10)
  @NotNull
  private String name;
}

现在,Spring 将在调用方法之前拦截其请求并对参数进行验证 —— 无需使用额外的手工测试。

另一种实现相同功能的方法是创建我们自己的注解。虽然你通常只在需要超出 Hibernate的内置约束集 时才使用自定义注解,本例中,我们假设 @Length 不存在。你可以创建两个额外的类来验证字符串长度,一个用于验证,一个用于对属性进行注解:

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = { MyAnnotationValidator.class })
public @interface MyAnnotation {

  String message() default "String length does not match expected";

  Class<?>[] groups() default {};

  Class<? extends Payload>[] payload() default {};

  int value();

}

@Component
public class MyAnnotationValidator implements ConstraintValidator<MyAnnotation, String> {

  private int expectedLength;

  @Override
  public void initialize(MyAnnotation myAnnotation) {
    this.expectedLength = myAnnotation.value();
  }

  @Override
  public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
    return s == null || s.length() == this.expectedLength;
  }
}

请注意,这些情况下,关注点分离的最佳实践要求在属性为 null 时,将其标记为有效(isValid 方法中的 s == null),如果这是属性的附加要求,则使用 @NotNull 注解。

public class TopTalentData {
  @MyAnnotation(value = 10)
  @NotNull
  private String name;
}

7. 错误七:(依旧)使用基于xml的配置

虽然之前版本的 Spring 需要 XML,但如今大部分配置均可通过 Java 代码或注解来完成;XML 配置只是作为附加的不必要的样板代码。

本文(及其附带的 GitHub 仓库)均使用注解来配置 Spring,Spring 知道应该连接哪些 Bean,因为待扫描的顶级包目录已在 @SpringBootApplication 复合注解中做了声明,如下所示:

@SpringBootApplication
public class Application {
  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}

复合注解(可通过 Spring 文档 了解更多信息)只是向 Spring 提示应该扫描哪些包来检索 Bean。在我们的案例中,这意味着这个顶级包 (co.kukurin)将用于检索:

  • @Component (TopTalentConverter, MyAnnotationValidator)
  • @RestController (TopTalentController)
  • @Repository (TopTalentRepository)
  • @Service (TopTalentService) 类

如果我们有任何额外的 @Configuration 注解类,它们也会检查基于 Java 的配置。

8. 错误八:忽略 profile

在服务端开发中,经常遇到的一个问题是区分不同的配置类型,通常是生产配置和开发配置。在每次从测试切换到部署应用程序时,不要手动替换各种配置项,更有效的方法是使用 profile。

考虑这么一种情况:你正在使用内存数据库进行本地开发,而在生产环境中使用 MySQL 数据库。本质上,这意味着你需要使用不同的 URL 和 (希望如此) 不同的凭证来访问这两者。让我们看看可以如何做到这两个不同的配置文件:

8.1. APPLICATION.YAML 文件

# set default profile to 'dev'
spring.profiles.active: dev

# production database details
spring.datasource.url: 'jdbc:mysql://localhost:3306/toptal'
spring.datasource.username: root
spring.datasource.password:

8.2. APPLICATION-DEV.YAML 文件

spring.datasource.url: 'jdbc:h2:mem:'
spring.datasource.platform: h2

假设你不希望在修改代码时意外地对生产数据库进行任何操作,因此将默认配置文件设为 dev 是很有意义的。然后,在服务器上,你可以通过提供 -Dspring.profiles.active=prod 参数给 JVM 来手动覆盖配置文件。另外,还可将操作系统的环境变量设置为所需的默认 profile。

9. 错误九:无法接受依赖项注入

正确使用 Spring 的依赖注入意味着允许其通过扫描所有必须的配置类来将所有对象连接在一起;这对于解耦关系非常有用,也使测试变得更为容易,而不是通过类之间的紧耦合来做这样的事情:

public class TopTalentController {

  private final TopTalentService topTalentService;

  public TopTalentController() {
    this.topTalentService = new TopTalentService();
  }
}

我们让 Spring 为我们做连接:

public class TopTalentController {

  private final TopTalentService topTalentService;

  public TopTalentController(TopTalentService topTalentService) {
    this.topTalentService = topTalentService;
  }
}

Misko Hevery 的 Google talk 深入解释了依赖注入的 “为什么”,所以,让我们看看它在实践中是如何使用的。在关注点分离(常见错误 #3)一节中,我们创建了一个服务和控制器类。假设我们想在 TopTalentService 行为正确的前提下测试控制器。我们可以通过提供一个单独的配置类来插入一个模拟对象来代替实际的服务实现:

@Configuration
public class SampleUnitTestConfig {
  @Bean
  public TopTalentService topTalentService() {
    TopTalentService topTalentService = Mockito.mock(TopTalentService.class);
    Mockito.when(topTalentService.getTopTalent()).thenReturn(
        Stream.of("Mary", "Joel").map(TopTalentData::new).collect(Collectors.toList()));
    return topTalentService;
  }
}

然后,我们可以通过告诉 Spring 使用 SampleUnitTestConfig 作为它的配置类来注入模拟对象:

@ContextConfiguration(classes = { SampleUnitTestConfig.class })

之后,我们就可以使用上下文配置将 Bean 注入到单元测试中。

10. 错误十:缺乏测试,或测试不当

尽管单元测试的概念已经存在很长时间了,但很多开发人员似乎要么 “忘记” 做这件事(特别是如果它不是 “必需” 的时候),要么只是在事后把它添加进来。这显然是不可取的,因为测试不仅应该验证代码的正确性,还应该作为程序在不同场景下应如何表现的文档。

在测试 Web 服务时,很少只进行 “纯” 单元测试,因为通过 HTTP 进行通信通常需要调用 Spring 的 DispatcherServlet,并查看当收到一个实际的 HttpServletRequest 时会发生什么(使它成为一个 “集成” 测试,处理验证、序列化等)。REST Assured,一个用于简化测试REST服务的 Java DSL,在 MockMVC 之上,已经被证明提供了一个非常优雅的解决方案。考虑以下带有依赖项注入的代码片段:

@RunWith(SpringJUnit4Cla***unner.class)
@ContextConfiguration(classes = {
    Application.class,
    SampleUnitTestConfig.class
})
public class RestAssuredTestDemonstration {

  @Autowired
  private TopTalentController topTalentController;

  @Test
  public void shouldGetMaryAndJoel() throws Exception {
    // given
    MockMvcRequestSpecification givenRestAssuredSpecification = RestAssuredMockMvc.given()
        .standaloneSetup(topTalentController);

    // when
    MockMvcResponse response = givenRestAssuredSpecification.when().get("/toptal/get");

    // then
    response.then().statusCode(200);
    response.then().body("name", hasItems("Mary", "Joel"));
  }

}

SampleUnitTestConfig 类将 TopTalentService 的模拟实现连接到 TopTalentController 中,而所有的其他类都是通过扫描应用类所在包的下级包目录来推断出的标准配置。RestAssuredMockMvc 只是用来设置一个轻量级环境,并向 /toptal/get 端点发送一个 GET 请求。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Spring Framework 5.0 入门教程

    1. 为什么学习Spring? 随着对Java EE的不断接触和理解,你会发现Spring 在各个企业和项目中发挥着越来越重要的作用.掌握Spring 已成为我们IT行业生存必学的本领之一. Spring Framework 是一个开源的Java/Java EE全功能栈(full-stack)的应用程序框架,以Apache许可证形式发布,也有.NET平台上的移植版本. 该框架基于 Expert One-on-One Java EE Design and Development(ISBN 0-76

  • 简单了解Spring Framework5.0新特性

    SpringFramework5.0是自2013年12月版本4发布之后SpringFramework的第一个主发行版.SpringFramework项目的领导人JuergenHoeller于2016年7月28日宣布了第一个SpringFramework5.0里程碑版本(5.0M1). 现在,将近一年的时间过去以后,我们期盼已久的RC3版本将于2017年7月18日发行.这是路线图规划中SpringFramework5.0首个GA发行版的最后一次发行. 从高层来看,SpringFramework5.

  • 使用Spring Framework 时常犯的十大错误(小结)

    1. 错误一:太过关注底层 我们正在解决这个常见错误,是因为 "非我所创" 综合症在软件开发领域很是常见.症状包括经常重写一些常见的代码,很多开发人员都有这种症状. 虽然理解特定库的内部结构及其实现,在很大程度上是好的并且很有必要的(也可以是一个很好的学习过程),但作为软件工程师,不断地处理相同的底层实现细节对个人的开发生涯是有害的.像 Spring 这种抽象框架的存在是有原因的,它将你从重复地手工劳作中解放出来,并允许你专注于更高层次的细节 -- 领域对象和业务逻辑. 因此,接受抽象

  • java使用@Transactional时常犯的N种错误

    目录 1.在同一个类中调用 2. @Transactional修饰方法不是public 3. 不同的数据源 4. 回滚异常配置不正确 5. 数据库引擎不支持事务 小结 @Transactional是我们在用Spring时候几乎逃不掉的一个注解,该注解主要用来声明事务.它的实现原理是通过Spring AOP在注解修饰方法的前后织入事务管理的实现语句,所以开发者只需要通过一个注解就能代替一系列繁琐的事务开始.事务关闭等重复性的编码任务. 编码方式确实简单了,但也因为隐藏了直观的实现逻辑,一些错误的编

  • 让PHP开发者事半功倍的十大技巧小结

    如果你使用一面大镜子作为冲浪板会发生什么?或许你会在较短的时间内征服海浪,但是你肯定从内心深处明白,这不是冲浪的正确选择.同样的道理也适用于PHP编程,尽管这样的类比听起来有一些古怪.我们经常听到有人试图用一个周末多点的时间来学会PHP,但是请恕我直言,这是学习这门编程语言的一种非常糟糕的方式. 为何说学习PHP的过程有别于任何其它语言? 就其本质而言,如果你掌握了以PHP语言"做事"的方式,那么在使用它时就会得心应手,因此值得你去投入精力去了解这些方式.在PHP中,单纯按照自己思路去

  • C语言编程时常犯十八个错误小结

    看着有错的程序,不知该如何改起,本人通过对C的学习,积累了一些C编程时常犯的错误,写给各位学员以供参考. 1.书写标识符时,忽略了大小写字母的区别. 复制代码 代码如下: main(){ int a=5; printf("%d",A);} 编译程序把a和A认为是两个不同的变量名,而显示出错信息.C认为大写字母和小写字母是两个不同的字符.习惯上,符号常量名用大写,变量名用小写表示,以增加可读性. 2.忽略了变量的类型,进行了不合法的运算. 复制代码 代码如下: main(){ float

  • 防范ASP木马的十大基本原则强列建议看下

    由于ASP它本身是服务器提供的一项服务功能,特别是最近由dvbbs的upfile文件出现漏洞以来,其高度的隐蔽性和难查杀性,对网站的安全造成了严重的威胁.因此针对ASP木马的防范和清除,为网管人员提出了更高的技术要求. 几个大的程序全部被发现存在上传漏洞,小程序更是不计其数,让asp木马一下占据了主流,得到广泛的使用,想必如果你是做服务器的话,一定为此头疼不止吧,特别是虚拟主机的用户都遇到过网页被篡改.数据被删除的经历,事后除了对这种行径深恶痛绝外,许多客户又苦于没有行之有效的防范措施.鉴于大部

  • Intellij IDEA十大快捷键

    Intellij IDEA中有很多快捷键让人爱不释手,stackoverflow上也有一些有趣的讨论.每个人都有自己的最爱,想排出个理想的榜单还真是困难.以前也整理过Intellij的快捷键,这次就按照我日常开发时的使用频率,简单分类列一下我最喜欢的十大快捷-神-键吧. 1 智能提示 Intellij首当其冲的当然就是Intelligence智能!基本的代码提示用Ctrl+Space,还有更智能地按类型信息提示Ctrl+Shift+Space,但因为Intellij总是随着我们敲击而自动提示,所

  • Python 十大经典排序算法实现详解

    目录 关于时间复杂度 关于稳定性 名词解释 1.冒泡排序 (1)算法步骤 (2)动图演示 (3)Python代码 2.选择排序 (1)算法步骤 (2)动图演示 (3)Python代码 3.插入排序 (1)算法步骤 (2)动图演示 (3)Python代码 4.希尔排序 (1)算法步骤 (2)Python代码 5.归并排序 (1)算法步骤 (2)动图演示 (3)Python代码 6.快速排序 (1)算法步骤 (2)动图演示 (3)Python代码 7.堆排序 (1)算法步骤 (2)动图演示 (3)P

  • 2004年十大网络安全漏洞

    国际安全组织新发布:2004年十大网络应用漏洞    IT安全专业人士的开放网络应用安全计划组织(OWASP)发布的第二份年度十大网络应用安全薄弱环节列表中,增加了"拒绝提供服务"类型的隐患,因为在去年该类型的隐患已屡见不鲜.OWASP的主席兼"奠基石"(一家提供战略安全服务的公司)顾问会主任柯费·马克称:"我们预测:本年度,主要的电子商务网站将遭到拒绝提供服务的攻击,因为黑客已经对众多的用户密码感到厌烦."比如:当一名掌握着大量电子邮件帐号的黑

  • 网上保障隐私十大秘技

    互联网是一把两刃利剑,一方面为日常生活带来便利,另一方面又为黑客入侵电脑系统开放更多渠道.一般网友应如何防范?请看下面能保障你隐私的十大秘技:  1. 采用匿名方式浏览,因为许多网站利用cookies跟踪网友的互联网活动,从而确定网友喜好.你可以在使用浏览器时关闭电脑接收cookie的选项,避免受到 cookies的追踪.  2. 进行任何网上交易或发送电邮前,切记阅读网站的隐私保护政策,因为有些网站会将你的个人资料卖给第三方.  3. 安装个人防火墙,以防止个人资料和财务数据被窃取.及时升级是

随机推荐