Skip to main content

04.springBoot的意图和注解

1 SpringBoot的意图

SpringBoot的意图就是实现"开闭原则",即已经实现功能的代码不改动,新增需求仍能被加进来.想象一下,在SpringBoot 中写代码时,如果先写接口类,然后再实现这些类, 从而做好了第一个版本的实现。
后面又有新的版本推出时,第2个版本只需要实现这些接口,就可以了,从而不用入侵式的修改已有的代码就可以实现增加的需求,遵守了"开闭 原则"
那么实现"开闭原则"非得要用控制反转实现的容器化吗?是的。以前如果不用容器化,直接new一个实现类,可以吗?可以的, 但, 这样的 代码不好维护,就是类想怎么写就怎么写以及后期怎么修改就怎么修改,由于没有接口类的约束,导致代码可读性差。那么加个接口类约束下可以吗?可 的,这样代码的可读性就高很多,典型的实现就是工厂模式了,如实现化一个工厂类,传入一个参数,在工厂类中实例化一个类返回出来,这样代码的可 读性是高了,但如果如果遇到了需求变更,如增加需求时,不得不在工厂类中修改代码,这不符合"开闭原则",那么有没有一种能获取到类似于工厂模式 的效果又不用侵入式去改动代码呢?有的,就是控制反转的实现--容器化和注入
给出一个接口类型和一个变量名,再由容器去实例化这个类,再根据这个接口类型找出关于这个接口的实现类,然后注入到这个变量名中。这样 看,由于有接口类型,就能知道这个被注入进来的类是怎么用的,这样不用侵入式修改代码就能实现工厂模式带来的可读性。换个角度看,可以把容器看成 是一个巨大的工厂模式,而业务代码就是这个工厂模式的参数而已,而在这个工厂中,分析着所有的业务代码,该实例化就实例化,该注入就注入。这就是 我认为的"容器化"的作用。
当然也有一些附加的优点,如单例模式带来的省内存,由于是共享的,再加个锁,可以起到多线程通信的作用。

2 注解的用法

@Component

@Component打上这个标记的类会自动注入到容器中,一般用于只是单纯地注入到容器中的目的。

@Service

@Service打上这个标记的类会自动注入到容器中,这个相对较于@Component,被用于服务层逻辑的处理类, 就是 写一些策略的实现类,然后被调用的,这样的用法。

@Repository

@Repository打上这个标记的类会自动注入到容器中, 这个相较于@Component,打上这个标记的类,表示这个类 是模型类,也就是对数据库操作的实现类。

@Configuration

SprintBoot注入时,并不能传入额外的初始化参数,而这些初始化的参数往往来自配置文件中,而@configuration就是把初始化交给开发者 来实现,从而达到配置的目的,如:
service.java文件中

package com.zhuche.server.service;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class TestTmp3 {
@Bean // 在这里打上@Bean 并返回需要被注入的实现类
public ITest testTmp4() {
return new TestTmp();
}
}

而在调用时,可以注入这个testTmp3的返回类,如:

package com.zhuche.server.api.v1;

import com.zhuche.server.service.ITest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletResponse;

@Controller
@ResponseBody
@RequestMapping("/v1/banner")
public class BannerController {
@Autowired
public ITest testTmp4; // <-- 在这里可以直接被注入进来

@RequestMapping(value = "/test", method = {
RequestMethod.GET,
})
public String test(HttpServletResponse response) {
System.out.println(this.getClass().getName());
testTmp4.hello();
return "success";
}
}

由于是开发者自己实例化的返回给容器,这样就可以有些类在初始化时,要传入参数时,可以直接传入进去。 这里适用于,把一些配置读取之后并传入到类中进行初始化。

@Lazy

打上这个标记的类,SpringBoot会在运行时才注入支容器中,一般不推荐使用

@confitionalOnProperty 条件注解

根据配置参数来决定Bean要不要注入到容器中

package com.zhuche.server.simple.hero;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;

@Component
@ConditionalOnProperty(value = "hero.condition", havingValue = "tmp", matchIfMissing = true)
public class Test {
public String Hello() {
return "hello worlld";
}
}

其意思是,如果有配置hero.condition等于tmp则注入进来,反之则不,如果没有相关配置,则默认注入

@Conditional 自定义条件注解

package com.zhuche.server.simple.hero;

import org.springframework.context.annotation.Conditional;
import org.springframework.stereotype.Component;

@Component
@Conditional(TestConditional.class) // <-- 自定义条件注解
public class Test {
public String Hello() {
return "hello worlld";
}
}
package com.zhuche.server.simple.hero;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class TestConditional implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// todo something <-- 业务逻辑来判断是否加入到容器中
return false;
}
}

@ConditionalOnBean(name = "mysql")

条件注解, 如果容器中mysqlBean则注入,

@ConditionalOnMissingBean(name = "mysql")

条件注解,其作用与@ConditionalOnBean相反.

@PathVariable路由参数和 @RequestParam 链接参数

package com.zhuche.server.api.v1;

import com.zhuche.server.dto.PersonDTO;
import com.zhuche.server.exception.http.ForbiddenException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;

@Controller
@ResponseBody
@RequestMapping("/banner")
public class BannerController {

@RequestMapping(value = "/test/{id}", method = {
RequestMethod.POST,
})
public String test(
@PathVariable Integer id,
HttpServletResponse response,
@RequestParam String name,
@RequestBody PersonDTO person
) {
System.out.println(this.getClass().getName());
System.out.println(id); // 路由参数
System.out.println(name); // 链接参数
System.out.println(person); // 请求体数据
throw new ForbiddenException(10001);
}
}

PersionDTO用户数据转对象

package com.zhuche.server.dto;

public class PersonDTO {
private String name;
private Integer age;

public String getName() {
return name;
}

c
return age;
}


public void setName(String name) {
this.name = name;
}

public void setAge(Integer age) {
this.age = age;
}

}

请求 POST http://localhost:8080/v1/banner/test/1?name=1 请求体:

{
"age": "18",
"name": "chuheng"
}

3 注解的默认原则

先是默认类型注入, 后再根据属性名注入。先是默认类型注入, 比如声明一个接口类,刚好有一个实现类,以这个接口为 注入类型的则会注入这么个类, 如果是2个类的实现呢?那么如果被注入的属性名刚好是实现类的同一名字,则会注入匹配对 的实现类,如果不是,则会报错,SpringBoot会要求在众多的实现类中标记一个@Primary的注解要求,这哪个类为 默认注入。