java常用注解总结

java常用注解大致是spring、mybatis、mybatisplus、jpa、springboot、springclould提供的一些注解。

一、日志类注解

@Slf4j 用于类上面,整个类里面就可以使用

log.info("错误信息,{}", 用户名称为空);
log.err("错误信息,{}", 用户名称为空);

此注解效果等同于 每个类里面写 private final Logger logger = LoggerFactory.getLogger(当前类名.class);


二、业务层类注解

@Service 用于类上面,将自动注册到Spring容器, 不需要再在applicationContext.xml文件定义bean
@Controller、@RestController 用于controller层,**controller上
@Service 用于service层,**seviceImpl上
@Repository 用于dao层,**daoImpl上
@Component 用于mapper层,**mapper上
@Mapper用于接口类上面,编译之后会生成相应的接口实现类,每个类都要加

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RestController;

// @Controller // 或者用下面这个
@RestController // 推荐这个
public class UserController {
}

############################

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class UserService {    
}

############################

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserDAO {
}

############################


import org.springframework.stereotype.Repository;

@Repository // 基于 @Component 实现
public interface UserRepository extends Repository<Users, Long>{
}
或者 
@RepositoryDefinition(domainClass = User.class, idClass = Long.class)
public interface UserRepository {
}

三、请求方法注解

@RequestBody 用于方法上面,请求参数格式化,转入javabean(用于POST请求)
@RequestParam 用于方法上面,传入单个参数,可以是否必传,设置默认值(用于GET请求)
@PathVariable 用于方法上面,是spring3.0的一个新功能:接收请求路径中占位符的值(分解url作为参数传入action内,用于GET请求)
@ResponseBody 用于方法上面,返回参数格式化(json格式),若@RestController用于类上,则不用该类里面的每个方法上都写上@ResponseBody 注解了,有点类似批量之意

import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;

// @Controller // 或者用下面这个
@RestController // 推荐这个
public class UserController {

    //@ResponseBody // 类上面的注解如果是@RestController 这个可省省略,因为@RestController已包含@ResponseBody的功效
    @PostMapping("/add")
    public void addUser(@RequestBody User user) {
    }

    /**
     * value="name"      调用放传入的参数名称是 name
     * defaultValue = “张三”  可省略。没传为空时,可以进行设置默认值为张三。
     * required = true   可省略。是否必传,默认是。所以非必填时,需要用这个设为false
     * @param name
     * @return
     */
    @GetMapping("/list")
    public List<User> listUser(@RequestParam(value = "name", defaultValue = "", required = false) String name){
        return new ArrayList<>();
    }
    
	@RequestMapping("showUser/{id}/{name}")
    public List<User> showUser(@PathVariable("id") Long id , @PathVariable("name") String name){
        return new ArrayList<>();
    }
}

@Param 一般用于dao接口层。有多个参数或者需要起别名时使用


import feign.Param;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface UserDAO {

    /**
     * select * from t_user where user_id = #{userId} and user_name = #{userName}
     * 只有一个参数时候,@Param注解可以不写, 有多个参数时如果没写则会报错
     * 他另外一个作用就是起别名,如下,上一层传进来是user_id,传到下一层去是userId,所以sql里面是#{userId}而不是#{user_id}
     * @param user_id
     * @param user_name
     * @return
     */
    User queryUser(@Param("userId") int user_id, @Param("userName") String user_name);

    /**
     * select * from t_user where user_name = #{vo.userName}
     * <if test="userIds != null and userIds.size!=0">  // userIds就是userIdList的别名
     *     and user_id in
     *     <foreach collection="userIds" close=")" open="(" separator="," item="id">
     *          #{id}
     *     </foreach>
     * </if>
     * 对象、list也可以用别名
     * @param user
     * @param userIdList
     * @return
     */
    List<User> queryUserList(@Param("vo") User user, @Param("userIds") List<Integer> userIdList);

}


四、注入类

@Autowired spring框架提供;先根据类型再根据名称查询;当接口有多个实现类时,搭配@Primary(优先使用哪一个) 或者 @Qualifier(指定使用哪一个)
@Resource java的jdk提供;先根据名称再根据类型查询;当接口有多个实现类时,通过name参数来指定使用哪一个

定义用户服务接口和两个实现类UserServiceImpl1和UserServiceImpl2

public interface UserService {
	void getName();
}

#####################################

@Service
public class UserServiceImpl1 implements UserService{
	@Override
	public void getName() {
		System.out.println("UserService1Impl");
	}
}
@Service
public class UserServiceImpl2 implements UserService {
	@Override
	public void getName() {
		System.out.println("userServiceImpl2");
	}
}

注入测试结果

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
public class UserController {

    /**
     * 报错:当UserService有多个实现类UserServiceImpl1和UserServiceImpl2同时存在时
     * 因为@Autowired先按照byType匹配,匹配到了“userServiceImpl1”和“userServiceImpl2”,结果不唯一。
     * 接下来只能按照byName注入,此时的name为“userService”,结果无法唯一确定,只能抛出异常
     */
    //@Autowired
    //private UserService userService;

    /**
     * 成功:因为先按照byType匹配不唯一,在根据byName,成功匹配到唯一的实现者 userServiceImpl1
     */
    //@Autowired
    //private UserService userServiceImpl1;

    /**
     * 成功:因为先按照byType匹配不唯一,在根据byName,而@Qualifier注解指定使用名称为 userServiceImpl1 的这一个
     */
    //@Autowired
    //@Qualifier("userServiceImpl1")
    //private UserService userService;



    /**
     * 报错:当UserService有多个实现类UserServiceImpl1和UserServiceImpl2同时存在时
     * 因为@Resource先按照byName匹配,此时的name为“userService”,没有匹配结果.
     * 接下来只能按照byType进行配置,此时又匹配到了“userServiceImpl1”和“userServiceImpl2”,结果不唯一,只能抛出异常
     */
    //@Resource
    //private UserService userService;

    /**
     * 成功:因为先按照byName匹配,直接匹配到唯一的实现者 userServiceImpl1
     */
    //@Resource
    //private UserService userServiceImpl1;

    /**
     * 成功:注解里面的参数name指定了实现者的名称
     */
    //@Resource(name = "userServiceImpl1")
    //private UserService userService;

    public void getName(){
        userService.getName();
    }
}

四、实体类以及对象bean

lombok里面的注解( ideal需要下载此插件),简化实体javabean工作量,只需要定义私有字段即可,编译时自动生成get/set等方法,如下注解常见
@Setter @Getter 写在类上面是给全部字段生成,写在某字段上只给某字段生成
@EqualsAndHashCode(callSuper = true)
@Accessors(fluent = true) 此注解有三个属性

fluent(值为布尔型),默认false,为true时省略get/set前缀,则是.name()而不是.getName()
chain(值为布尔型),默认false,为true生成的set方法返回this,false时set是void类型
prefix(值为字符型),get/set去掉指定的前缀。如@Accessors(prefix = “user”)是.getName()而不是.getUserName()

@ToString
@Data 集成以上几个注解 ,一般使用这一个注解就足够了。

比如用于接收请求参数的请求对象


import lombok.*;
import lombok.experimental.Accessors;

import java.util.Date;

//@Setter
//@Getter
//@Accessors(fluent = true)
//@ToString
@Data  // 用这一个就行了
@EqualsAndHashCode(callSuper = true) // 默认为false, 若new两个user对象里面字段属性值不同,当false时候比较结果相同,当true时比较结果不相同
public class UserDTO {

    private Integer id;
    private String name;
    private Date createDate;
    private String notExistStr;
    private String sex;
    
}

所需依赖是

<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <optional>true</optional>
</dependency>

实体类的注解,取决于持久层用的是什么技术,比如Hibernate、jpa、mybatis、mybatis-plus等,用的不同,其注解也略有差异。就像数据库mysql、oracle、sqlserver一样,语法总体一样,但个别语法、函数有差别。

对象映射到数据库的数据表
@Entity 说明是实体类,并且使用默认的orm规则(即类名即数据库表中表名,class字段名即表中的字段名)
@Table(name = “t_user”) 用于实体类上 ,Hibernate使用。一般搭配@Entity一起用
@TableName(“t_user”) 用于实体类上 ,mybatis-plus使用
若类名和数据库的表名相同,则可以省略括号;不相同则需用括号来绑定影射表名

@TableName // 说明数据库的表名称就是user,和类名一致
@TableName(“t_user”) // 数据库的表名称就是t_user,和类名user不一致,所以需要告知t_user的字段都映射到User这个实体类。这个比较常见通用
@TableName(value = “t_user”) // 上面是简略写法,这个是完整的写法


表示主键字段
@Id Hibernate使用。一般搭配@GeneratedValue使用
@GeneratedValue(strategy = GenerationType.AUTO) 用于标注主键的生成策略,通过strategy 属性指定策略。默认是AUTO自动增长,全部值如下
@TableId(value = “id”, type = IdType.AUTO) mybatis-plus使用

其中GenerationType的全部枚举如下

IDENTITY:采用数据库ID自增长的方式来自增主键字段,Oracle 不支持这种方式;
AUTO:JPA自动选择合适的策略,是默认选项;
SEQUENCE:通过序列产生主键,通过@SequenceGenerator 注解指定序列名,MySql不支持这种方式
TABLE:通过表产生主键,框架借由表模拟序列产生主键,使用该策略可以使应用更易于数据库移植。

其中IdType的全部枚举如下

AUTO(0)数据库ID自增,确保数据库设置了 ID自增 否则无效
NONE(1) 为未设置主键类型(注解里等于跟随全局,全局里约等于INPUT)
INPUT(2) 用户输入ID 该类型可以通过自己注册自动填充插件进行填充
以下2种类型、只有当插入对象ID 为空,才自动填充。
ASSIGN_ID(3) 分配ID (主键类型为number或string) 默认实现类 {@link
com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(雪花算法)
ASSIGN_UUID(4) 分配UUID (主键类型为 string) 默认实现类 {@link
com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(UUID.replace(“-”,“”))


表示非主键的普通字段,映射表字段名称和实体变量名称的对应关系。
@TableField 用于变量上,(一般表字段是下划线格式,实体变量是驼峰格式,用于mybatis plus)
属性如下

  • exist 表示是否为是数据库字段 非数据库字段设置这个属性为false,不设置则插入查询试会报错
  • select 表示是否查询该字段。加上不参与查询,并且返回null
  • 其他

@Column 用于变量上面,表示表字段和实体变量的对应关系,类似TableField
其他属性如下

  • name 定义了被标注字段在数据库表中所对应字段的名称;
  • unique 表示该字段是否为唯一标识,默认为false
  • nullable 表示该字段是否可以为null值,默认为true
  • length 表示字段的长度,当字段的类型为varchar时,该属性才有效,默认为255个字符
  • precision和scale 表示精度,当字段类型为double时,precision表示数值的总长度,scale表示小数点所占的位数。

如果实体变量存在数据库字段,且没其他特殊要求,这两个注解可以省略不写。

@JsonIgnoreProperties(ignoreUnknown = true) 用于类上,用来在实体类序列化和反序列化的时候忽略该字段字段,数据转换时候如果找不到对应的字段会报错,此注解忽略对应不上的,就不会报错了

hebernate实体注解demo如下

import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.walmart.aloha.canary.entity.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;
import org.springframework.data.annotation.Transient;
import org.springframework.format.annotation.DateTimeFormat;

import javax.persistence.*;
import javax.validation.constraints.*;
import java.math.BigDecimal;
import java.util.Date;

//@Setter
//@Getter
//@Accessors(fluent = true)
//@ToString
@Data  // 用这一个就行了
@EqualsAndHashCode(callSuper = false)
//@DynamicInsert //置为true,表示insert对象的时候,生成动态的insert语句,如果这个字段的值是null就不会加入到insert语句中。
//@DynamicUpdate //设置为true,表示update对象的时候,生成动态的update语句,如果这个字段的值是null就不会被加入到update语句中。

//@Table // 说明数据库的表名称就是user,和类名一致
@Table(name = "t_user") // 只能完整的写法,不能省略name这个属性
@Entity // 有它就得必然有@Id注解
public class User extends BaseEntity {
    /**
     * 表示这个字段是表的主键
     */
    @Id
    @GeneratedValue // 默认主键自增(也就是GenerationType.AUTO),所以可以省略
    //@GeneratedValue(strategy = GenerationType.AUTO) // 或者指定主键策略
    private Long id;

    /**
     * 这个注解一般不使用,因为数据库是怎么字段名称,我们就定义为什么实体变量
     */
    //@Column 一般省略不写
    //@Column(name = "user_name", length = 100, nullable = false) // 数据库的字段名称是user_name不是name, 长度不允许超过100, 不允许为空
    private String name;

    /**
     * 表示notExistStr这个字段不存在t_user表里面
     * 非表里面的字段需要添加此注解, 否则,ORM框架默认其注解为@Basic,当查询、插入时sql会报错这个字段找不到
     */
    @Transient
    private String notExistStr;

    /**
     * 时间类型字段,设置时间和格式。非必须, 但建议使用
     * 但没设置时间格式, 返回的值年月日和时分秒之间有个字母T
     * 但没设置时间时区,返回的值可能不正确,比如数据库是20210103 00:00:00,但接口返回是20210102 00:00:00,相差了一天
     */
    @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") // 将Date转换成String
    //@DateTimeFormat(pattern = "yyyy-MM-dd") // 将String转换成Date 用这个也可以进行时间格式化
    private Date createDate;

    /**
     * 忽略这个字段
     */
    @JsonIgnore
    @Enumerated
    private String sex;

    /**
     * 用于变量上,改变返回字段名称,比如实体定义的变量是nn但接口返回的却是ww
     */
    @JsonProperty("ww")
    private String nn;


    // 以下是校验注解

    /**
     * 最大最小值校验 年纪要求是18-150岁
     */
    @Min(18) // 类似的 @DecimalMin("45")
    @Max(150) // 类似的 @DecimalMax("45")
    // @Size(min = 18 ,max = 150) 或者这样写 在或者用 @Range
    private int age;

    /**
     * 必须是一个过去的时间。比当前时间小
     * 类似的还有 @Future 必须是一个未来的时间。比当前时间大
     *
     * 要求数据类型必须是date类型 表明在持久化过程中将birthday对应的数据存为DATE(即只有“年月日”)
     */
    @Past
    @Temporal(TemporalType.DATE) //  或者 @Column(columnDefinition="DATE") columnDefinition的值和数据库一样的
    private Date birthDay;

    /**
     * 长度校验
     * 正则校验
     */
    @Length(min = 11, max = 11, message = "手机号码必须11位")
    @Pattern(regexp = "1\d{10}", message = "手机号码格式不正确")
    private String mobile;

    /**
     * 必填,不允许为空
     * 必须是邮箱格式
     */
    @NotNull(message = "邮箱不能为空")
    @Email
    private String email;

    //@Digits(Integer, fraction) // 必须是数字
    //@AssertFalse // 值必须是 false
}

mybatis-plus实体注解demo如下

import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.walmart.aloha.canary.entity.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;

import javax.persistence.Entity;
import java.util.Date;

//@Setter
//@Getter
//@Accessors(fluent = true)
//@ToString
@Data  // 用这一个就行了
@EqualsAndHashCode(callSuper = true) // 默认为false, 若new两个user对象里面字段属性值不同,当false时候比较结果相同,当true时比较结果不相同

//@TableName // 说明数据库的表名称就是user,和类名一致
@TableName("t_user") // 推荐用这个。数据库的表名称就是t_user,和类名user不一致,所以需要告知t_user的字段都映射到User这个实体类。
//@TableName(value = "t_user") // 上面是简略写法,这个是完整的写法
public class User extends BaseEntity {
    /**
     * 表示这个字段是表的主键,AUTO表示是自增的
     */
    @TableId(value = "id", type = IdType.AUTO) // 推荐用这个。比较常见
    //@TableId(value = "id") // 省略type表示用默认的主键自增
    //@TableId // 表示数据库的主键字段名称就id, 和实体变量名id完全一致
    private Integer id;

    /**
     * 这个注解一般不使用,因为数据库是怎么字段名称,我们就定义为什么实体变量
     */
    //@TableField // 一般省略不写
    //@TableField(value = "user_name") // 说明数据库的字段名是user_name 不是 name
    //@TableField(value = "user_name", updateStrategy = FieldStrategy.IGNORED) // 对于更新操作,不做任何处理(可以设置不允许空,不更新这个字段)
    private String name;

    /**
     * 表示notExistStr这个字段不存在t_user表里面
     * 非表里面的字段需要添加此注解, 否则查询、插入时sql会报错这个字段找不到
     */
    @TableField(exist = false)
    private String notExistStr;

    /**
     * 自动填充值(默认不填充) 在使用 fill 字段填充策略时,需要实现处理才能生效
     * 插入操作时,自动填充createTime字段,需要自己写个实现类。参考我写的 MybatisObjectHandler
     */
    @TableField(value = "create_time", fill = FieldFill.INSERT)
    private Date createTime;
}

填充实现类

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * 将MybatisObjectHandler注入spring容器中,操作数据添加或者修改时,自动给指定字段添加数据
 */
@Component
public class MybatisObjectHandler implements MetaObjectHandler {

    // 指定的字段名称,表示在insert场合可以操作该方法
    @Override
    public void insertFill(MetaObject metaObject) {
        setFieldValByName("createtime", new Date(),metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        setFieldValByName("updateTime",new Date(),metaObject);
    }
}

五、枚举类
基于实体类的注解,枚举也可以省点代码
@NoArgsConstructor //无参注解,也可以用于实体对象
@AllArgsConstructor //全参注解,也可以用于实体对象

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public enum SexEnum {

    MAN(1, "男"),
    WOMAN(2, "女");

    private int code;
    private String value;

}

六、配置类

@Value(“${}”) 用于变量上,读取配置文件里面的值赋给给变量

import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Setter
@Getter
@Component
public class MyConfig { // 读取配置的类,需要用配置项的service,通过@Autowired MyConfig使用这里的变量

    @Value("${my.app.name}") // 取配置文件里面my.app.name的值
    private String appName;
    @Value("${my.app.name:test}") // 取配置文件里面my.app.name的值,如果取不到默认为test
    private String appName1;
}

或者直接读取整个配置文件,映射到字段上面


import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Configuration // 表示这是一个配置文件的类
@PropertySource(value="classpath:merchant.properties") // 表示这个类对应的配置文件是merchant.properties
@ConfigurationProperties(prefix="sdk") // 表示配置文件里面的参数都有前缀sdk, 例如sdk.requestUrl=www.123.com
@Setter
@Getter
public class FileConfig {

    private String requestUrl;
}


七、启动类注解

直接看启动类代码demo

import org.mybatis.spring.annotation.MapperScan;
import org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@SpringBootApplication // springboot的启动类注解,包含了@SpringBootConfiguration、@EnableAutoConfiguration 和@ComponentScan,开启了包扫描、配置和自动配置的功能

@Configuration // 相当于传统的xml配置文
@ImportResource(locations = { "classpath:jdbcTemplate.xml", "classpath : yyyy.xml"} }) // 加载文件 若只有一个文件 @ImportSource("classpath : jdbcTemplate.xml")这样也可. 绝对路径写法是 @ImportSource(locations= {"file : d:/test/jdbcTemplate.xml"})
@MapperScan("com.yulisao.dao") // 如果没有,则每个mapper接口类上需添加@Mapper注解
@ServletComponentScan
// 表示将该类自动发现(扫描)并注册为Bean,可以自动收集所有的Spring组件,包括@Configuration类。我们经常使用@ComponentScan注解搜索beans,并结合@Autowired注解导入。
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class, MybatisAutoConfiguration.class}) //排除默认的DatasourceAutoConfiguration

// 以下是在启动类上开启,具体的方法上还有对应的注解与之搭配。若启动类上没开启,则对应的注解不起作用
@EnableAsync // 开启异步
@EnableFeignClients // 开启远程调用Feign
@EnableScheduling // 开启定时任务
@EnableTransactionManagement // 开启事务
@EnableCaching // 开启缓存
@EnableRetry // 开启请求重试机制
public class MyDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyDemoApplication.class, args);
    }
}

八、功能类注解

@EnableFeignClients 用于启动类上,表示启动feign客户端
@FeignClient 用于接口类上,表示这个是feign客户端,无需写实现类。

@FeignClient(name = "yourName")
public interface MyTestClient {
    
    @PostMapping("/***/***/queryCount") // post请求
    Long queryCount(@RequestBody ReqDto reqDto);

    
    @GetMapping(value ="/***/***/queryUser") //get请求
    Long queryUser(@RequestParam("name") String name);
}

调用的时候

@Autowired
private MyTestClient myTestClient;

方法里面写 Long count = myTestClient.queryCount(reqDto);

@EnableScheduling 用于启动类上,表示项目支持定时任务
@Scheduled 用于方法上,表示该方法定时执行
用法
@Scheduled(fixedRate = 5000) 每隔5秒执行一次
@Scheduled(cron = “0 23 20 ? * *” ) 每天23.20执行一次

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.util.Date;

@Service
public class TestService {

    @Scheduled(cron = "0/10 * * * * ?") // 每隔10秒执行
    //@Scheduled(fixedRate = 5000) //每隔5秒执行一次
    public void process() {
        System.out.println(new Date());
    }
}

@EnableAsync 用于启动类上,表示项目支持异步方法调用
@Async 用于方法上,表示该方法为异步方法,即该方法和调用者不在一个线程中进行

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.Date;

@Service
public class TestService {
    
    public void test() {
        System.out.println("主线程执行中");
        process(); // 异步执行后续逻辑
    }

    @Async    
    public void process() {
        System.out.println(new Date());
    }
}


@EnableRetry 用于启动类上, 表示接口支持重试
@Retryable 用于方法上,表示这个方法如果没请求成功会自动重新发起请求。支持设置重试次数,每次的时间间隔。底层通过while循环实现,若不支持幂等性的接口慎用

import lombok.extern.slf4j.Slf4j;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;

import java.util.Date;

@Slf4j
@Service
public class UserService {

    /**
     * value:抛出异常时触发重试
     * include:抛出指定异常时候,触发重试。比如连接超时异常,不同时刻重试有可能成功。他和value一样,默认为空,当exclude也为空时,默认所有异常都触发 exclude = TimeoutException.class
     * exclude:抛出指定异常时候,不触发重试。 比如空指针异常一般是程序逻辑或者数据问题,重试再多次也一样失败。 include = {NullPointerException.class, IndexOutOfBoundsException.class}
     * maxAttempts:最大重试次数,默认3次。第一次请求也算一次,所以如果失败了,后续还会重试2次
     * backoff:重试等待策略,默认使用@Backoff,
     * @Backoff的value默认为1000L,单位是毫秒。如下是设置为2000毫秒,表示第一次失败后,休眠2000毫秒进行第一次重试
     * @Backoff的multiplier表示指定延迟倍数,默认为0,根据这个计算出第二次、第三次....重试的时间。
     * 例如 首次失败后,则第一次重试为2秒,第二次为2*1.5=3秒,第三次为3*1.5=4.5秒
     * 建议合理设置重试次数和间隔。设置过多的重试次数可能会导致系统资源浪费,设置过小的间隔可能导致重试失败。
     * @param user
     * @throws Exception
     */
    @Retryable(value = Exception.class, maxAttempts = 5, backoff = @Backoff(delay = 2000L,multiplier = 1.5))
    public void add(User user) throws Exception {
        System.out.println("执行中,时间是:" + new Date());
        // dosomething 业务逻辑略
        throw new Exception(); // testing
    }

    /**
     * 上面的最大重试次数maxAttempts用完后还没请求成功,则会执行这个方法(如果没有这个方法则抛出异常结束)
     * @Retryable注解会找@Recover注解进行匹配,所以这两个注解修饰的方法的返回类型,入参需要一致才能匹配上,且在同一个类中
     * 第一个入参需是异常类,和@Retryable的保持一致即可
     * @param e
     */
    @Recover
    public void recover1(Exception e) {
        log.info("回调方法执行,比如发个邮件告警");
    }

    /**
     * 这一个不会被匹配执行, 因为返回数据类型是int,和上面@Retryable注解修饰的add方法的返回类型void不一致
     * @param e
     * @return
     */
    @Recover
    public int recover2(Exception e) {
        log.info("回调方法执行,比如发个短信告警");
        return 0;
    }
}

他的所需依赖是

<dependency>
  <groupId>org.springframework.retry</groupId>
  <artifactId>spring-retry</artifactId>
 </dependency>

@EnableRetry 用于启动类上, 表示支持数据结果缓存
@Cacheable 用于方法上,表示当请求参数完全一致时,就不执行方法了直接从缓存返回结果即可。
@CachePut 类似@Cacheable,但是总会执行方法并把最新结果更新到缓存
@CacheEvict 清理掉缓存

import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;

/**
	 * 请求参数一致时,直接从缓存中拿,就不执行方法去查库了
	 * value / cacheNames 用来指定缓存名,二者选其一即可。 多个缓存空间时,value = {"name1", "name2"}
	 * key / keyGenerator 用来指定缓存的key是什么 缺省时则会自动生成一个key。
	 * 比如 key = "#userName" 表示用userName这个入参做key,比如 key = "#p0" 表示用该方法的第一个参数做key,key = "'id-' + #user.id"表示用id-用户id做key,比如 key = "'hash' + #user.hashCode()"用整个user对象做key
	 * cacheManager / cacheResolver 指定缓存管理器(spring 默认提供是的SimpleCacheResolver)。比如你想用redis的缓存
	 * sync 是否同步。多线程同时刻调用,都请求到数据库了,也就是稍微的缓存击穿。 设置为ture可以避免
	 * condition 对请求参数判断在决定是否要进行缓存。 比如请求参数中年纪小于18的不缓存 condition = ”#age > 18“
	 * unless 对返回参数判断在决定是否要进行缓存。 比如返回结果中年纪小于18的不缓存 unless = ”#age < 18“
	 *
	 * @param userName
	 * @return
	 */
	@Cacheable(value = {"yourCacheName"}, key = "#userName")
	public User queryUser(String userName) {
		return null;
	}

	// 会执行方法查库并把最新结果存缓存里 根据用户的id作为key缓存在yourCacheName里面,但是用户名称是zhang的数据不缓存起来
	@CachePut(value = {"yourCacheName"}, key = "#user.id", unless = "#result.username eq 'zhang'")
	public User queryUser2(User user) {
		return null;
	}

	//beforeInvocation=false表示在方法执行之后且用户名称是zhang则移除缓存
	@CacheEvict(value = {"yourCacheName"}, key = "#user.id", beforeInvocation = false,  condition = "#result.username eq 'zhang'")
	public User queryUser3(User user) {
		return null;
	}

@EnableTransactionManagement 用于启动类上, 表示项目支持事务
@Transactional用于方法上
用法
@Transactional
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)


@ServletComponentScan 用于启动类上,
Servlet、Filter、Listener可以直接通过@WebServlet、@WebFilter、@WebListener注解自动注册实现

@WebServlet(name="TestServlet",urlPatterns="/mytest")
public class TestServlet extends HttpServlet { 
}

写法很多
@ComponentScan({“com.yulisao.dao”,“com.yulisao.dao1”})
@ComponentScan(basePackages = {“com.yulisao.dao”,“com.yulisao.dao1”})
@ComponentScan(“com.yulisao”)
@ComponentScan(value = “com.yulisao”)
@ComponentScan(basePackageClasses = 要扫描类.class)



测试类注解

写一个单一的测试类,下面的执行顺序是 @BeforeClass -> @Before -> @Test -> @After -> @Before -> @Test -> @After -> @AfterClass

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Before;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import java.io.IOException;

@AfterClass
@RunWith(SpringRunner.class) // 固定
//@TestPropertySource("classpath:${test.prop:my-test.properties}") // 使用的配置文件路径
@AutoConfigureMockMvc // mvc的web项目需要
@SpringBootTest // 也可以加上 (classes = { 启动类.class })
public class MyTest {
	protected final Logger logger = LoggerFactory.getLogger(getClass());

	@Autowired
	protected MockMvc mockMvc;

	protected MockHttpSession mockHttpSession;


	/**
	 * 整个测试类第一个执行,只执行一次,且方法必然被static修饰
	 * @throws IOException
	 */
	// @BeforeAll  Junit 5写法
	@BeforeClass // Junit 4写法
	public static void beforeClass() throws IOException {
		System.out.println("运行开始时只执行一次 beforeClass ");
	}

	/**
	 * 每个@test方法执行之前都会被执行一次
	 * @throws IOException
	 */
	//@BeforeEach // Junit 5写法
	@Before("") // Junit 4写法
	public void Before() throws IOException {
		System.out.println("每个方法执行之前都执行一次 Before ");
	}


	/**
	 * 测试方法,一个类中可以有多个测试方法,按从上至下顺序执行
	 * @throws Exception
	 */
	@Test
	public void test1() throws Exception {
		System.out.println("测试方法1");
	}
	@Test
	public void test2() throws Exception {
		System.out.println("测试方法2");
	}

	/**
	 * 每个@test方法执行之后都会被执行一次
	 * @throws IOException
	 */
	//@AfterEach // Junit 5写法
	@After("") // Junit 4写法
	public void After() throws IOException {
		System.out.println("每个方法执行之后都执行一次 After ");
	}

	/**
	 * 整个测试类最后已个执行,只执行一次,且方法必然被static修饰
	 * @throws IOException
	 */
	// @AfterAll  Junit 5写法
	@AfterClass
	public static void afterClass() throws IOException {
		System.out.println("运行结束时只执行一次 afterClass ");
	}
}

或者写一个抽象的测试类(里面可以写点公共通用的方法、日志打印等), 测试不同的功能时候继承实现这个抽象测试类


import org.junit.jupiter.api.BeforeEach;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;


@RunWith(SpringRunner.class) // 固定
//@TestPropertySource("classpath:${test.prop:my-test.properties}") // 使用的配置文件路径
@AutoConfigureMockMvc // mvc的web项目需要
@SpringBootTest // 也可以加上 (classes = { 启动类.class })
public abstract class MyTest {
	protected final Logger LOG = LoggerFactory.getLogger(getClass());

	@Autowired
	protected MockMvc mockMvc;

	protected MockHttpSession mockHttpSession;

	@BeforeEach
	public void setupMockMvc() {
		mockHttpSession = new MockHttpSession();
	}

	/**
	 * 发起请求的公共方法
	 * @param url 请求地址
	 * @param content 请求参数
	 * @return
	 * @throws Exception
	 */
	protected String post(String url, String content) throws Exception {
		MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.post(url)
				.accept(MediaType.ALL).session(mockHttpSession)
				.contentType(MediaType.APPLICATION_JSON).content(content);
		MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse();
		response.setCharacterEncoding("UTF-8");
		return response.getContentAsString();
	}
}

#### 实现者 ###

import com.alibaba.fastjson.JSONObject;
import com.yulisao.MyTest;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.springframework.beans.factory.annotation.Autowired;

public class UserControllerTest extends MyTest {

    /**
     * 实现类里面直接注入 Controller 进行调用, 跟调用service方法类似
     */
    @Autowired
    @InjectMocks
    UserController userController;

    @Test
    public void addUser() throws Exception {
        // 组装请求参数
        User param = new User();
        param.setUserName("aa");

        // 直接通过 Controller调用 发起请求
        userController.addUser(param);
        // 或者 模拟调用接口地址 发起请求
        post("/user/addUser", JSONObject.toJSONString(param));
    }
}

切面类注解(功能类似拦截器)

@Aspect 用于类上, 声明这个是一个切面类
@Pointcut 定义切入点
@Around 定义切入的功能方法

比如为全部请求打印请求返回参数并登记数据库形成请求日志

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.yulisao.common.http.RespBody;

import javax.servlet.http.HttpServletRequest;

import java.util.Date;

@Aspect // 声明是切面类
@Configuration
public class ControllerLogAspect {

    private static final Logger LOG = LoggerFactory.getLogger(ControllerLogAspect.class);

    @Autowired
    private LogsService logsService;

    // 拦截全部action请求,也就是项目里面的全部的控制类
    final String regex = "execution(* com.yulisao.controller..*.*(..))";

    // 定义切点Pointcut
    @Pointcut(regex)
    public void excudeService() {
    }

    // 切入后在这里做什么事情,实现什么功能
    @Around("excudeService()") // 上面切点注解修饰的方法名称以及入参
    public Object Around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    	RespBody<?> resp = null; // 全部控制类的返回结果的公共部分 ?表示每个接口返回的具体对象数据
        try {
            LOG.info("执行目标方法之前...");
            Date startTime = new Date();
            resp = (RespBody<?>)proceedingJoinPoint.proceed(); // 执行目标方法
            Date endTime = new Date();
            LOG.info("执行目标方法之后...");
            
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            LOG.info("目标对象为:" + proceedingJoinPoint.getTarget());
            LOG.info("返回结果为:" + resp.getMessage());

            // 组装参数入库记录保存
            LogsAspectDTO logsAspectDTO = new LogsAspectDTO();
            logsAspectDTO.setArgs(proceedingJoinPoint.getArgs());
            logsAspectDTO.setRequest(request);
            logsAspectDTO.setStartTime(startTime);
            logsAspectDTO.setEndTime(endTime);
            logsAspectDTO.setResult(resp.getMessage());
            logsService.insertOperateLogs(logsAspectDTO);
        } catch (Exception e) {
            LOG.error("环绕增强方法异常:{}",e);
        }finally {
            return resp;
        }
    }
}

再比如请求次数限制注解拦截切面

import net.jodah.expiringmap.ExpirationPolicy;
import net.jodah.expiringmap.ExpiringMap;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Aspect
@Component
public class LimitRequestAspect {
    private Logger log = LoggerFactory.getLogger(this.getClass());
    private static ConcurrentHashMap<String, ExpiringMap<String, Integer>> book = new ConcurrentHashMap<>();

    private List<String> spcUrlList = Arrays.asList(
            "/file/upload",
            "/register"
    );

    @Pointcut("@annotation(limitRequest)") // 切入点是@LimitRequest这个注解
    public void excudeService(LimitRequest limitRequest) {
    }

    @Around("excudeService(limitRequest)") // 上面的方法名称以及入参limitRequest
    public Object doAround(ProceedingJoinPoint pjp, LimitRequest limitRequest) throws Throwable {

        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletRequest request = sra.getRequest();
        String ip = getIpAddr(request);
        String url = request.getServletPath();
        log.info("request url is " + url);
        log.info("request ip is " + ip);

        String prefix = getPathPrefix(url);
        if (StringUtils.isNotBlank(prefix)) {
            url = prefix;
        }

        ExpiringMap<String, Integer> uc = book.getOrDefault(url, ExpiringMap.builder().variableExpiration().build());
        Integer uCount = uc.getOrDefault(ip, 0);
        log.info("request uCount is " + uCount);

        if (uCount >= limitRequest.count()) { // 超过次数,不执行目标方法
            throw new Exception("请求过于频繁,请稍后在试");
        } else if (uCount == 0){ // 第一次请求时,设置有效时间
            uc.put(ip, uCount + 1, ExpirationPolicy.CREATED, limitRequest.time(), TimeUnit.MILLISECONDS);
        } else { // 未超过次数, 记录加一
            uc.put(ip, uCount + 1);
        }
        book.put(url, uc);

        return pjp.proceed();
    }

    public static String getIpAddr(HttpServletRequest request) {
        String ipAddress = request.getHeader("x-forwarded-for");
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("Proxy-Client-IP");
        }
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getRemoteAddr();
            if (ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")) {
                InetAddress inet = null;
                try {
                    inet = InetAddress.getLocalHost();
                } catch (UnknownHostException e) {
                    e.printStackTrace();
                }
                if (inet!=null)
                    ipAddress = inet.getHostAddress();
            }
        }
        if (ipAddress != null && ipAddress.length() > 15) {
            if (ipAddress.indexOf(",") > 0) {
                ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
            }
        }
        return ipAddress;
    }

    private String getPathPrefix(String url) {
        for(int i=0; i < spcUrlList.size(); i++){
            Pattern pattern = Pattern.compile(spcUrlList.get(i));
            Matcher matcher = pattern.matcher(url);
            if(matcher.find()){
                return spcUrlList.get(i);
            }
        }

        return null;
    }
}

配置文件类注解

@ActiveProfiles 一般用于测试类上,表示使用哪个配置文件
@Profiles 用于类或者方法上面,表示是配置之一

@ActiveProfiles("dev") // 表示使用 dev 这个配置
public class MyTest {
 
    @Test
    public void test() {
        action.execute("use dev config");
    }
}

 
 ####  配置文件, 也可能是写在pom文件里面 ###
@Configuration
public class ProjectConfig {
 
    @Bean
    @Profile("dev") // 开发环境配置
    public AppConfig config1(){
        // 略
    }
 
    @Bean
    @Profile("uat") // uat环境配置
    public AppConfig config2(){
        // 略
    }
} 

sql查询类注解

# Repository 	
@Query("from t_user p where p.name = ?1 and p.age = ?2")
User queryUser(String name, String age); // 顺序必须和上面一致

@Query("from t_user p where p.name = :name and p.age = :age")
User queryUser(@Param("age") Integer age, @Param("name") String name); // 顺序不要求上面一致

@Query(value = "select count(*) from t_user p where p.name = :name and p.age = :age", nativeQuery = true) // nativeQuery = true表示支持原生sql查询
Long queryUserCount(@Param("age") Integer age, @Param("name") String name); 

# mybatis 类似的还有@insert @update @delete
@Select("<script>select * from t_user where age=#{age} <if test='name != null'> and name=#{name} </if> order by create_time desc </script>")
User queryUser(@Param("age") Integer age, @Param("name") String name);

注解内的注解(自己写自定义注解时候才会用到)

@Documented:表示该注解是否可以生成到 API文档中。可以理解为导出API文档时文档化
@Target :用于描述注解的使用范围

@Target 配合ElementType枚举类型一起使用。
TYPE : 类型上面 用于描述类、接口(包括注解类型) 或enum声明
FIELD : 用于描述字段
METHOD :方法
PARAMETER : 参数 【参数名】
CONSTRUCTOR : 构造方法
LOCAL_VARIABLE : 局部变量
ANNOTATION_TYPE : 可以打在注解上面
PACKAGE :可以打在包上面
TYPE_PARAMETER : 参数类型【形式参数类型】
TYPE_USE : 任何位置都可以

@Retention:用于描述注解的生命周期,也就是说这个注解在什么范围内有效。

@Retention配合RetentionPolicy枚举类型一起使用。
SOURCE :源码中
CLASS :表示 一个注解可以在源码中,并且可以在字节码文件中
RUNTIME :表示 一个注解可以在源码、字节码、及运行时期该注解都会存在

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface TransactionCode {
    String value() default "";
}

如上的Target 表示作用于方法之内,Retention表示运行时候也有效。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值