啰嗦一句,可以先猜测,这个注解的含义,实现原理,然后查看相关文献,相关用法,以及自己如何做个性化的内容,大部分的技术学习都应该走类似的路线。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Excel
{
/**
* 导出时在excel中排序
*/
public int sort() default Integer.MAX_VALUE;
/**
* 导出到Excel中的名字.
*/
public String name() default "";
/**
* 日期格式, 如: yyyy-MM-dd
*/
public String dateFormat() default "";
/** 最后登录时间 */
@Excel(name = "最后登录时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Type.EXPORT)
private Date loginDate;
很容易猜想,导出Excel,做了些处理,比如这个字段的name是“最后登录时间” width是30,dateFormat格式是“yyyy-MM-dd HH:mm:ss”
String dateFormat = field.getAnnotation(Excel.class).dateFormat();
if (StringUtils.isNotEmpty(dateFormat))
{
val = parseDateToStr(dateFormat, val);
}
字段找到注解为dateFormat,也就是某个类属性上面是否有这个注解,如果有这样的注解,就做个性化的动作,基本验证应该没有问题,接下来看官方的解释。
在Annotation的源码注释中有说明:所有的注解类型都需要继承该公共接口,本质上看注解是接口,但是代码并没有显式声明继承关系,可以直接查看字节码文件;
-- 1、声明注解
public@interface Excel {}
-- 2、查看指令
javap -v Excel.class
-- 3、打印结果
Compiled from "Excel.java"
public interface com.base.test.Excel extends java.lang.annotation.Annotation
声明注解时使用,用来定义注解的作用目标,保留策略等;
元注解就是用于定义注解的注解,通常用于注解的定义上,标明该注解的使用范围、生效范围等。元XX 都代表最基本最原始的东西,因此,元注解就是最基本不可分解的注解,我们不能去改变它只能使用它来定义自定义的注解。
元注解包含以下五种:
其中最常用的是@Retention和@Target下面分别介绍一下这五种元注解。
中文翻译为保留的意思,标明自定义注解的生命周期
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
从编写Java代码到运行主要周期为源文件→ Class文件 → 运行时数据,@Retention则标注了自定义注解的信息要保留到哪个阶段,分别对应的value取值为SOURCE →CLASS→RUNTIME。
中文翻译为目标,描述自定义注解的使用范围——作用的目标是谁。也就是指明,你的注解到底是用来修饰方法的?修饰类的?还是用来修饰字段属性的。
允许自定义注解标注在哪些Java元素上(类、方法、属性、局部属性、参数…)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface MyAnnotation {
String description() default "";
}
@MyAnnotation
public class AnnotationTest {
// @MyAnnotation 用在属性上则会报错
public String name;
@MyAnnotation
public void test(){}
}
value是一个数组,可以有多个取值,说明同一个注解可以同时用于标注在不同的元素上。value的取值如下
值 | 说明 |
TYPE | 类、接口、注解、枚举 |
FIELD | 属性 |
MEHOD | 方法 |
PARAMETER | 方法参数 |
CONSTRUCTOR | 构造函数 |
LOCAL_VARIABLE | 局部变量(如循环变量、catch参数) |
ANNOTATION_TYPE | 注解 |
PACKAGE | 包 |
TYPE_PARAMETER | 泛型参数 jdk1.8 |
TYPE_USE | 任何元素 jdk1.8 |
是否可以被标注类的子类继承。被@Inherited修饰的注解是具有继承性的,在自定义的注解标注到某个类时,该类的子类会继承这个自定义注解。这里需要注意的是只有当子类继承父类的时候,注解才会被继承,类实现接口,或者接口继承接口,都是无法获得父接口上的注解声明的。正确的示例如下(通过反射获取注解)
是否可以重复标注。这个注解其实是一个语法糖,jdk1.8之前也是有办法进行重复标注的,就是使用数组属性(自定义注解会讲到)。
是否在生成的JavaDoc文档中体现,被标注该注解后,生成的javadoc中,会包含该注解,这里就不做演示了。
这里涉及到两个核心概念:反射机制、动态代理;反射机制可以在程序运行时获取类的完整结构信息,代理模式给目标对象提供一个代理对象,由代理对象持有目标对象的引用;网上搜到的例子:
先来看一个简单的注解使用案例,再细致的分析其中原理,案例并不复杂,就是常见的标注与解析两个关键动作;
publicclass LogInfo {
@SystemLog(model = "日志模块")
public static void main(String[] args) {
// 生成代理文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 反射机制
Method[] methods = LogInfo.class.getMethods();
for (Method method:methods){
SystemLog systemLog = method.getAnnotation(SystemLog.class) ;
if (systemLog != null){
// 动态代理:com.sun.proxy.$Proxy2
System.out.println(systemLog.getClass().getName());
System.out.println(systemLog.model());
}
}
}
}
这里涉及到两个核心概念:反射机制、动态代理;反射机制可以在程序运行时获取类的完整结构信息,代理模式给目标对象提供一个代理对象,由代理对象持有目标对象的引用;
案例中通过反射机制,在程序运行时进行注解的获取和解析,值得关注的是systemLog对象的类名,输出的是代理类信息;
案例执行完毕后,会在代码工程的目录下生成代理类,可以查看$Proxy2文件;
publicfinalclass $Proxy2 extends Proxy implements SystemLog {
public final String model() throws {
try {
return (String)super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
thrownew UndeclaredThrowableException(var3);
}
}
}
在对SystemLog解析的过程中,实际上是在使用注解的代理类,$Proxy2继承了Proxy类并实现了SystemLog接口,并且重写了相关方法;有关反射和代理的逻辑,在之前的内容中有详说,此处不赘述;
值得一看是代理类中invoke方法调用,具体的处理逻辑在AnnotationInvocationHandler类的invoke方法中,会对注解原生方法和自定义方法做判断,并对原生方法提供实现;
总结一下:
反射可以获取到Class对象,进而获取到Constructor、Field、Method等实例,点开源码结构发现Class、Constructor、Field、Method等均实现了AnnotatedElement接口,AnnotatedElement接口的方法如下
// 判断该元素是否包含指定注解,包含则返回true
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
// 返回该元素上对应的注解,如果没有返回null
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
// 返回该元素上的所有注解,如果没有任何注解则返回一个空数组
Annotation[] getAnnotations();
// 返回指定类型的注解,如果没有返回空数组
T[] getAnnotationsByType(Class<T> annotationClass)
// 返回指定类型的注解,如果没有返回空数组,只包含直接标注的注解,不包含inherited的注解
T getDeclaredAnnotation(Class<T> annotationClass)
// 返回指定类型的注解,如果没有返回空数组,只包含直接标注的注解,不包含inherited的注解
T[] getDeclaredAnnotationsByType
// 返回该元素上的所有注解,如果没有任何注解则返回一个空数组,只包含直接标注的注解,不包含inherited的注解
Annotation[] getDeclaredAnnotations();
这就说明以上元素均可以通过反射获取该元素上标注的注解。
可以跟踪一下Excel。
重点是掌握标准注解,其一:理解和掌握标准注解;其二:在合适的场景写自定义注解。
在JDK中有多个注解是经常使用的,例如Override、Deprecated、SuppressWarnings等;
这里注意FunctionalInterface注解,从1.8开始引入,检验是否为函数式接口,即接口只能有一个抽象方法,否则编译报错;
在具体的看Lombok组件之前,需要先了解一个概念:代码编译;在open-jdk的描述文档中大致分为三个核心阶段;
第一步:读取命令行上指定的所有源文件,解析为语法树,进行符号表填充;
第二步:调用注解处理器,如果处理器生成任何新的源文件或类文件,编译会重新启动;
第三步:分析器创建的语法树被分析并转换为类文件;
更多细节说明可以参考openjdk文档中Compiler模块的内容,下面再回到Lombok组件上;
Lombok组件在代码工程中的使用非常频繁,通过注解的方式极大的简化Java中Bean对象的编写,提高了效率并且让源码显得简洁;
这里用一段简单的代码演示其效果,在IdKey的类中通过三个常用的Lombok注解,替代了类中很多基础方法的显式生成,查看编译后的文件实际是存在相关方法的;
@Data
@AllArgsConstructor
@NoArgsConstructor
publicclass IdKey {
private Integer id ;
private String key ;
public static void main(String[] args) {
IdKey idKey01 = new IdKey(1,"cicada") ;
System.out.println(idKey01);
idKey01.setId(2);
idKey01.setKey("smile");
System.out.println(idKey01);
}
}
这里需要了解JDK中注解处理器的相关源码,AbstractProcessor作为超类,编译器在编译时会去检查该类的子类,子类中最核心的是process方法;
-- 1、Lombok处理器
@SupportedAnnotationTypes("*")
publicclass LombokProcessor extends AbstractProcessor {
private JavacTransformer transformer;
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
transformer.transform(prio, javacProcessingEnv.getContext(), cusForThisRound, cleanup);
}
}
-- 2、AST抽象树
publicclass JavacTransformer {
public void transform(long priority, Context context, List<JCTree.JCCompilationUnit> compilationUnits,
CleanupRegistry cleanup) {
JavacAST ast = new JavacAST(messager, context, unit, cleanup);
ast.traverse(new AnnotationVisitor(priority));
handlers.callASTVisitors(ast, priority);
}
}
-- 3、注解处理抽象类
publicabstractclass JavacAnnotationHandler<T extends Annotation> {
public abstract void handle(AnnotationValues<T> annotation, JCAnnotation ast, JavacNode annotationNode);
}
-- 4、Getter注解处理
publicclass HandleGetter extends JavacAnnotationHandler<Getter> {
@Override
public void handle(AnnotationValues<Getter> annotation, JCTree.JCAnnotation ast, JavacNode annotationNode) {
JavacNode node = annotationNode.up();
List<JCTree.JCAnnotation> onMethod = unboxAndRemoveAnnotationParameter(ast, "onMethod", "@Getter(onMethod", annotationNode);
switch (node.getKind()) {
case FIELD:
createGetterForFields(level, fields, annotationNode, true, lazy, onMethod);
break;
}
}
}
IdKey类从简洁的源码编译为复杂的字节码文件,通过注解对结构处理时关联一个核心概念,叫AST抽象树,会涉及到很多语法、词法的解析逻辑;
1、Spring Bean类的注解
1.1 @Component
通用的注解,可标注任意类为 Spring 组件。如果一个 Bean 不知道属于哪个层,可以使用@Component 注解标注。
1.2 @Repository
对应持久层即 Dao 层,主要用于数据库相关操作。
1.3 @Service
对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。
1.4 @Controller
对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面。
1.5@RestController
@RestController = @Controller + @ResponseBody
写这一个注解就相当于写了后面的两个注解,在返回值是json串的非常方便,但同时也会有一个问题,加了这个注解就不能返回jsp或者html页面,这时可以通过返回视图数据的方式来返回页面。
ModelAndView mv = new ModelAndView("index");
return mv;
1.6 @Configuration
一般用来声明配置类。
指示一个类声明一个或多个@Bean方法,并且可以由Spring容器处理,以便在运行时为这些bean生成BeanDefinition和服务请求。
@Configuration
public class AppConfig {
@Bean
public MyBean myBean() {
// instantiate, configure and return bean ...
}
}
注意:
1.@Configuration不可以是final类型;
2.@Configuration不可以是匿名类;
3.嵌套的configuration必须是静态类。
2、注入数据注解
2.1 @Autowired
自动按照类型注入,只要容器中有唯一一个 bean 对象类型和要注入的变量类型匹配,就可以注入成功。
如果有多个类型匹配,先匹配类型,然后根据变量名称去对应,如果变量名称和 bean 名称相同,就注入。
2.2 @Resource
byName就是变量名去匹配bean的id属性,而byType则是变量类型去匹配bean的class属性。
<bean id="userService" class="com.test.UserServiceImpl">
</bean>
@Autowired和@Resource的区别:
@Autowired注解是Spring提供的,而@Resource注解是J2EE本身提供的;
@Autowird注解默认通过byType方式注入,而@Resource注解默认通过byName方式注入;
@Autowired注解注入的对象需要在IOC容器中存在,否则需要加上属性required=false,表示忽略当 前要注入的bean,如果有直接注入,没有跳过,不会报错。
2.3 @Value
用于注入基本类型和 String 类型的数据。
3、请求常用注解
3.1 @RequestMapping
它用于映射客户端的访问地址,可以被应用于类和方法上面。
可以指定请求的类型get,post,put,delete
@Controller
@RequestMapping("/index")
public class HelloWorldController {
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public String hello() {
return "/WEB-INF/views/success.jsp";
}
@RequestMapping(value = "/world", method = RequestMethod.POST)
public String world() {
return "/WEB-INF/views/success.jsp";
}
}
@RequestMapping 还可以将多个请求映射到一个方法上,只需要给 value 来指定一个包含多个路径的列表。
@Controller
@RequestMapping("/index")
public class HelloWorldController {
@RequestMapping(value = {"/hello", "/world", "/helloworld"})
public String hello() {
return "/WEB-INF/views/success.jsp";
}
}
3.2 @GetMapping
等价于@RequestMapping(value="",method=RequestMethod.GET)
3.3 @PostMapping
等价于@RequestMapping(value="",method=RequestMethod.POST)
3.4 @PutMapping(value="/users/{userId}")
等价于@RequestMapping(value="/users/{userId}",method=RequestMethod.PUT)
3.5 @DeleteMapping("/users/{userId}")
等价于@RequestMapping(value="/users/{userId}",method=RequestMethod.DELETE)
4、前后端传值
4.1 @PathVariable
用于获取路径参数,通过 @PathVariable 可以将 URL 中占位符参数绑定到控制器处理方法的入参中。
RequestMapping("user/get/mac/{macAddress}")
public String getByMacAddress(@PathVariable String macAddress){
//can do something;
}
4.2 @RequestParam
用于controller层,是Spring的注解,用于获取查询参数,解决前台参数名称与后台接收参数变量名称不一致的问题。
public String login(@RequestParam(value = "username") final String username,
@RequestParam(value = "password",required = false) final String password,
@RequestParam(value = "valcode",required = false) final String valcode) {
}
value:参数名字,即入参的请求参数名字,如username表示请求的参数区中的name为username的参数的值将传入;
required:是否必须,默认是true,表示请求中一定要有相应的参数,否则将报404错误码;
defaultValue:默认值,表示如果请求中没有同名参数时的默认值,默认值可以是SpEL表达式,如“#{systemProperties['java.vm.version']}”。
4.3 @Param
用于dao层,是mybatis中的注解。
使得mapper.xml中的参数与后台的参数对应上,也增强了可读性。
如果两者参数名一致得话,spring会自动进行封装,不一致的时候就需要手动去使其对应上。 即:用注解来简化xml配置的时候,@Param注解的作用是给参数命名,参数命名后就能根据名字得到参数值,正确的将参数传入sql语句中 。
List<Employee> getAllEmployeeByPage(@Param("page") Integer page,
@Param("size") Integer size);
5、表相关注解
5.1 @Entity
声明一个类对应一个数据库实体。
@Entity说明这个class是实体类,并且使用默认的orm规则,即class名就是数据库表中表明,class字段名即表中字段名。@Entity注解指明这是一个实体Bean。在项目启动时会根据该类自动生成一张表,表的名称即@Entity注解中name的值,如果不配置name,默认表明为类名所有的实体类都要有主键,@Id注解表示该属性是一个主键,@GeneratedValue注解表示注解自动生成,strategy则表示主键的生成策略。
@Data
@Entity(name = "t_book")
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "book_name")
private String name;
@Column(name = "book_author")
private String author;
private Float price;
@Transient
private String description;
}
5.2 @Table
@Table注解默认情况下只会完成表和实体之间的映射,声明才对象映射到数据库的数据表,通过它可以为实体指定表(table)。
常用属性:name 指定表 @Table(name = "book")
@Data
@Table(name="book")
public class Book{
@Id
private Integer id;
@Column(name="book_name")
private String name;
...
}
5.3 @Id
声明一个字段为主键。所有的实体类都要有主键,@Id注解表示该属性是一个主键。
5.4 @Column
声明字段,可以设置字段的属性,字段名、是否为空。
生成的表中字段的名称就是实体类中属性的名称,通过@Column注解可以定制生成的字段属性,name表示该属性对应的数据表中字段的名称,nullable表示该字段非空。
5.5 @Transient
声明不需要与数据库映射的字段,在保存的时候不需要保存进数据库。
@Transient注解表示在生成数据库中的表时,该属性被忽略,即不生成对应的字段。
6、事务注解@Transactional
@Transactional(rollbackFor = Exception.class) //这里回滚进行定义
public int update(Prdtv prdtv) throws RuntimeException{
//注意在这里处理业务时,不要使用Try ...异常捕获,否则不回滚
return prdtvMapper.update(prdtv);
}
@Transactional:事务注解,注解一般用在可以作用在类或者方法上
1.类上,表明类中所有 public 方法都启用事务 2.方法上,最常用的一种 3.接口上,不推荐使用
@Transactional注解只能在抛出RuntimeException或者Error时才会触发事务的回滚,常见的非RuntimeException是不会触发事务的回滚的。但是我们平时做业务处理时,需要捕获异常,所以可以手动抛出RuntimeException异常或者添加rollbackFor = Exception.class(也可以指定相应异常)
事务的隔离级别:是指若干个并发的事务之间的隔离程度
1. @Transactional(isolation = Isolation.READ_UNCOMMITTED):读取未提交数据(会出现脏读,
不可重复读) 基本不使用
2. @Transactional(isolation = Isolation.READ_COMMITTED):读取已提交数据(会出现不可重复读和幻读)
3. @Transactional(isolation = Isolation.REPEATABLE_READ):可重复读(会出现幻读)
4. @Transactional(isolation = Isolation.SERIALIZABLE):串行化
7、AOP注解
7.1 @After 在方法执行之后执行(方法上)
@After("pt()")
public void after(){
}
7.2 @Before 在方法执行之前执行(方法上)
@Before("pt()")
public void before(){
}
7.3 @Around 在方法执行之前与之后执行(方法上)
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object ret = pjp.proceed();
return ret;
}
7.4 @AfterReturning当前方法作为返回后通知
@AfterReturning(value="pt()",returning = "ret")
public void afterReturning(Object ret) {
}
7.5 @AfterThrowing当前方法作为异常后通知
@AfterThrowing(value="pt()",throwing = "t")
public void afterThrowing(Throwable t){
}
@Aspect:声明一个切面
@PointCut:声明切点
@EnableAspectJAutoProxy:开启Spring对AspectJ代理的支持
8、Lombok
8.1 @Setter
注解在属性上。为属性提供 setting 方法。
8.2 @Getter
注解在属性上。为属性[欢迎转载听雨的人博客]提供 getting 方法。
8.3 @Data
注解在类上。等同于添加如下注解: @Getter/@Setter生成get,set方法 @ToString 生成toString方法 @EqualsAndHashCode 生成equals和hashcode方法 @RequiredArgsConstructor 生成一个指定名称的静态方法
public class Programmer{
@Getter
@Setter
private String name;
@Setter(AccessLevel.PROTECTED)
private int age;
@Getter(AccessLevel.PUBLIC)
private String language;
}
相当于:
public class Programmer{
private String name;
private int age;
private String language;
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
protected void setAge(int age){
this.age = age;
}
public String getLanguage(){
return language;
}
}
9、MyBatis中常用注解
9.1 @Insert(sql语句):实现新增
public interface UserDAO{
@Options(useGeneratedKeys = true,keyColumn = "uid", keyProperty = "id")
@Insert("insert into t_user(username,password,gender,birth) values(#{username},#{password},#{gender},#{birth})")
public void insertUser(User user);
}
设置@Options属性userGeneratedKeys的值为true,并指定实例对象中主键的属性名keyProperty=“id”,以及在数据库中的字段名keyColumn=“uid”。这样在user插入数据后,userId属性会被自动赋值
9.2 @Update(sql语句):实现更新
public interface UserDAO{
@Update("update t_user set username=#{usernmae},password=#{password}")
public void updateUser(User user);
}
9.3 @Delete(sql语句):实现删除
public interface UserDAO{
@Delete("delete from t_user where id = #{id}")
public void deleteUserById(@Param("id") int id);
}
9.4 @Select(sql语句):实现查询
public interface UserDAO{
@Select("select * from t_user where id = #{id} and username = {#username}")
public User queryUserbyId(@Param("id") int id,@Param("username") String username);
}
其他MyBatis注解:
@Result(property="实体类属性的名称",jdbcType=数据库字段类型):实现结果集封装
@Results(column="数据库字段名",porperty="实体类属性名",jdbcType="数据库字段数据类型"):封装多个结果集(当数据库字段名与实体类对应的属性名不一致时,可以使用@Results映射来将其对应起来)
@ResultMap(@Results的id值):实现引用@Results 定义的封装
@One:实现一对一结果集封装
@Many:实现一对多结果集封装
@CacheNamespace:实现注解二级缓存的使用
10、其他
1.@Scope注解默认的singleton单例模式。
@Scope注解是springIoc容器中的一个作用域,在 Spring IoC 容器中具有以下几种作用域:基本作用域singleton(单例)、prototype(多例),Web 作用域(reqeust、session、globalsession),自定义作用域。
2.Enable注解说明*
@EnableAsync: 开启异步方法的支持。
@EnableScheduling: 开启计划任务的支持。
@EnableAspectAutoProxy:开启对AspectJ自动代理的支持
@EnableAsync:开启异步方法的支持
@EnableScheduling:开启计划任务的支持
@EnableWebMvc:开启web MVC的配置支持
@EnableConfigurationProperties:开启对@ConfigurationProperties注解配置Bean的支持
@EnableJpaRepositories:开启对SpringData JPA Repository的支持
@EnableTransactionManagement:开启注解式事务的支持
@EnableCaching:开启注解式的缓存支持