声明
此文十分十分浅显,只在于告诉你这些常用注解有什么用,怎么用,为什么要用。但这恰恰是敲代码的第一步,谁不是从码农当起的呢?有能力的话建议读 Spring 的源码,它是开源的,每个人都会有不同的收获,每个人都会有自己的理解。此文更偏向于学习笔记,如有错漏请联系作者改正
Spring项目常用架构
你不需要知道为什么这么架构,你只用知道大家都这样做,行业称之为"规范"。
一般的 Spring 项目都至少拥有以下几个包,并使用对应的注解注册为 Spring 的 Bean,由 Spring 统一进行管理。
目录结构
project
│
└── common
│
├── controller
│
├── dao
│
└── exception
│
└── pojo
│ └── DTO
│
└── service
│ └── impl
│
└── utils
其他层(utils / common / exception)
utils与common主要存放工具类,exception存放错误处理类,这里先不做讲解。
实体类层(pojo / entity / model)
用于存放实体类,一般的,一个实体类对应数据库里的一张表。
来看一个简单的User类实例吧。
/**
* 用户类
*
*/
@Table(name = "table_user")
@Entity
@Data
public class User {
@Id //设置主键为用户id
@GeneratedValue(strategy = GenerationType.IDENTITY)//以自增方式保存
@Column(name = "user_id")
private Integer userId;
@Column(name = "user_name")
private String userName;
@Column(name = "user_password")
@JsonIgnore //不参与json序列化
private String userPassword;
@Column(name = "email")
private String email;
@Column(name = "creat_date",updatable = false)
@CreationTimestamp
private Date creatDate;
}- 我们先看上面三个直接注册到类上的注解:
@Table(name = "table_name"):声明此类映射到数据库的表名,表名命名一般为小写单词且以_分割,例如 typecho 的数据库表名以 typecho_ 开头。@Entity:声明并注册此类为实体类。@Data:自动生成此类的get,set,toString等常用方法。
- 以下为注册到属性的注解:
@Id:声明此字段为数据库中表的主键,缺少此注解可能会导致项目报错。@GeneratedValue(strategy = GenerationType.IDENTITY):创建字段时以自增方式保存,常用于用户id,实体计数等场景。@Column(name = "user_name"):解决属性与字段命名规范不同,将此属性映射为指定的字段名。updatable 属性设定该字段是否能被修改,本例中此注解注册到注册日期字段上,因此设定为不可修改。@JsonIgnore:前后端使用json通信时,一些敏感数据我们并不希望直接返回(如密码等),此注解可在后端将返回的对象转为json数据格式时忽略此属性,将无敏感数据的json发送到前端。与之对应的是 @JsonInclude(JsonInclude.Include.ALWAYS) 可强制属性参与序列化@CreationTimestamp:创建该日期字段时,自动获取并保存为当前时间。
实体类DTO层(DTO)
在更为规范的项目中,通常还会为类创建一个对应的DTO类,常用于进行更规范的数据传输,可以理解为实体类的简化。
看看上面User类的UserDTO类:
/**
* 数据传输用户类
*/
@Data
public class UserDTO {
@NotBlank(message = "用户名不能为空!")
private String userName;
@NotBlank(message = "密码不能为空!")
@Length(min = 8,max = 16)
private String userPassword;
@Email(message = "请输入正确的邮箱!")
private String email;
}观察可得,类简化了绝大部分,类上只有 @Data 注解,只留下了可能会接受到数据的属性,但是多了一些没见过的注解。我们说过DTO类用来传输数据,那如果接受到的数据不合法呢?因此我们在此类引入了数据校验注解。@NotBlank(message = "message):不允许此键值为空,非法时输出 message。@Length(min = 8,max = 16):校验此键值长度,允许长度最短值为 min ,长度最大值为 max。@Email(message = "message"):校验此键值是否为正确的邮箱格式,参数非法时输出 message。
在DTO层做参数校验后,前端和业务方法中遍可以省略部分参数校验。该try还是要try的(
控制器层(controller)
重要类,后端为前端提供的访问数据的接口,接受前端发送的 json 数据转换为对象到后端进行处理,并将处理后的对象重新转为 json 数据格式以供前端提取,一般来说返回值是含有状态码,提示信息,以及实体数据的 json 数据格式。狭义上就是我们口中的 api。
来看一个 UserController 类的实例吧:
@RestController //将接口方法返回的对象转成json
@RequestMapping("/user") //将此web路径映射到此Controller类
@CrossOrigin(origins = "*") //解决跨域访问问题
public class UserController {
@Autowired
IUserService userService;
@PostMapping("/register")
public ResponseMessage addUser(@Validated @RequestBody UserDTO user) {
//To do
}
}- 老样子先看上面三个直接注册到类上的注解。
@RestController:是@Controller和@ResponseBody的结合,前者声明并注册该类为一个控制器类,后者将该类返回的对象转为 json 格式,前后端借此使用 json 数据格式进行数据交换。不难看出此注解适用于异步请求的控制器。@RequestMapping("path"):将此后端地址映射到此类上,比如上文中,访问 "项目地址/user" 时会查找并调用此类下对应的方法。为 web 路径和类、方法添加映射关系。@CrossOrigin(origin = "URL"):你可能听说过跨域限制,此注解用于放行指定URL跨域访问,也是为了适配异步请求。在项目实例中需要指定前端项目地址,防止恶意访问。 位于类中,方法外的注解
@Autowired:将注册到 Srping 且被扫描到的 Bean 自动装配到此类中,相当于创建一个实例对象。本例中将业务层的IUserService 接口类进行装配,之后若需要调用方法,直接使用userService.methods接口回调调用此接口的Impl实现类中的方法。引入此注解后无需重复创建实例对象,交由 Spring 管理。@Postmapping:@RequestMapping的特例。访问括号中的 web 路径时若使用 post 类型的请求,则映射到此方法上。本例中的实际地址为项目地址/user/register。Post 改为 Get 则接受 get 类型的请求,省略括号则接受此类下的任何 web 路径。括号中的路径可动态使用{方法参数}如@GetMapping("{userId}") public ResponseMessage getUser(@PathVariable Integer userId) { //To do }- 方法参数中的两个接口
@Validated:声明此方法的参数需要经过校验。刚刚在DTO类中我们只是配置了数据的校验格式,这里才是启用数据校验。@RequestBody:将接受到的 json 数据格式转换为可直接被 java 处理的对象类型,便于操作。
数据访问层(dao / mapper)
直接对类,或者说映射为表的类,进行数据库的增删改查操作。
@Repository
public interface UserRepository extends CrudRepository<User,Integer> {
boolean existsByUserNameOrEmail(String userName, String email);
//...
}@Repository:声明并注册此类为一个数据访问类。
这个类非常简单,大部分增删改查的方法父类已经帮我们实现了,只需要继承CrudRepository<User,Integer>,尖括号中User为类名,Integer为User数据表的主键类型。
此类中可以不写代码,也可以自定义数据库操作语句,只需要模仿其提供的方法格式即可。格式大概为 返回值类型 查询方式By查询所用字段(所需字段参数);参考本例。
业务层(service)
@Service
public interface IUserService{
//TO do
}@Service:声明并注册此类是一个业务类。
此类作为接口只书写抽象方法,为了规范,实现方法一般另起一个impl包。
业务实现层(impl)
@Service
@Slf4j
public class UserServiceImpl implements IUserService{
//TO do
}- 此类多了一个奇怪名字的注解
@Slf4j:由于业务层的方法实现逻辑复杂,常常需要打印错误日志,注册此注解后可直接调用 log 的方法打印日志。
一些其他常用注解
@Component:和@Service @Controller @Reponsitory注解一样,都是将类注册为 Spring 的 Bean。之所以将它们分开也是为了规范,用名字表明它们各自的职责。@Resource:和@Autowired注解类似。区别是前者默认先根据名称注入,后者默认先根据类型注入。一般来说后者更常用。@Transactional:将此方法注册为事务,也就是数据库概念中的那个事务。要么全部执行,要么出错回滚全部不执行。@Configuration:声明此类是一个注册文件,相当于可以用 Java 书写的 Spring 的xml配置文件。@ComponentScan(value = "path"):指定扫描 path 路径下的所有组件(Component)并注册为 Spring 的 Bean,包括@Configuration。一般注册到项目的启动类。
本文所用依赖
至此许多常见依赖你都有了最基本浅显的认识,能简单的使用它们了。可能你会发现许多注解无法使用,甚至报错。这其实是因为需要额外安装依赖,这是很正常的,项目中的 pom.xml 职责正是如此。下列为本例中展示代码所用到的依赖。
<!-- 操作数据库 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- 自动封装getter setter toString方法 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>annotationProcessor</scope>
</dependency>
<!-- 参数校验 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>