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")
条件注解, 如果容器中mysql
的Bean
则注入,
@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
的注解要求,这哪个类为
默认注入。