前言
注解是JDK1.5出现的一个重要的功能,自面世之后其简化开发、使用方便等特点也收到广大程序员的青睐,更是在各大框架中广泛地应用。相信各位读者在写java代码中也肯定会使用到类似于
@Override
、@Autowire
等注解,那么你是否知道这些注解到底是如何发挥其作用的呢?你是否会根据实际的项目需要来自定义注解呢?本篇文章将对注解的生效原理以及如何自定义注解来进行讲解,希望对各位读者有所帮助。
说在前面
在讲自定义注解之前,需要对AOP技术和反射部分的知识有所了解,因为注解之所以可以实现简化我们操作的作用,在各个框架中大放异彩,实际上靠的就是JDK的反射和Spring的AOP技术。但本文并不会涉及AOP技术的介绍,所以如果你对AOP的知识还不够了解的话,建议还是先看我的这篇文章:Spring系列(三)——Spring的AOP开发,之后再回头看本篇文章:
一、了解反射
由于注解生效的前提是反射技术(AOP只是能够增强使用注解的方便程度,但非必要条件),所以在介绍注解时我们先来看一下反射技术都有些什么作用
通过无参构造函数创建对象
/*
use no arg constructor to new instance
*/
public static void noArgInstance() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class<?> aClass = Class.forName("com.xiaoming.reflex.User");
User user = (User)aClass.newInstance();
user.setAge(15);
user.setName("xiaoming");
System.out.println(user);
}
通过有参构造函数创建对象
/*
use arg constructor to new instance
*/
public static void argInstance() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<?> aClass = Class.forName("com.xiaoming.reflex.User");
Constructor<?> constructor = aClass.getConstructor(String.class, Integer.class);
User user = (User)constructor.newInstance("xiaoming", 16);
System.out.println(user);
}
通过反射给对象属性设值
/*
set field value by reflex
*/
public static void setFieldValue() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
Class<?> aClass = Class.forName("com.xiaoming.reflex.User");
User user = (User) aClass.newInstance();
Field name = aClass.getDeclaredField("name");
Field age = aClass.getDeclaredField("age");
// 需要注意,对于私有的方法和变量,需要设置其权限
name.setAccessible(true);
name.set(user,"xiaolan");
age.setAccessible(true);
age.set(user,10);
System.out.println(user);
}
通过反射调用对象方法
/*
invoke private method
*/
public static void getPrivateMethod() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
Class<User> userClass = (Class<User>) Class.forName("com.xiaoming.reflex.User");
User user = userClass.newInstance();
Method login = userClass.getDeclaredMethod("login",String.class);
login.setAccessible(true);
login.invoke(user,"小黑");
}
可能有些读者看上去会觉得反射用起来十分的麻烦,直接new
一个对象难道不香吗?确实,从简易程度上来说,直接new一个对象当然更加方便,但是如果是想调用私有方法呢?那么常规的做法就无法满足需要了。同时,反射可以使得代码在运行时去加载类,举个例子,我们想要在代码运行过程中加载一个类,但是这个类只有在运行的时候才能确定,那么很明显,new
这种创建对象的形式肯定是不可以满足条件的,但反射就能够做到这种事情。
实际上,通过上面的代码演示,我们可以知道,反射可以让我们获取到类对象的相关信息,这其中还包括可以获取类对象中的反射属性。关于更多信息,我们不妨接着看下一小节认识一下什么是注解。
二、了解注解
相信大多数人最早接触注解应该是了解@Override
注解的时候,知道了这个注解可以帮助我们标识当前方法覆盖了其父类的方法。而后我们了解到,为了实现配置的解耦,我们采用了XML的方式,来分离配置和业务代码,从而更好地管理我们的项目。再后来,开发人员发现XML虽然实现了解耦合,但是开发起来太麻烦了,于是我们开始使用注解来简化我们的开发。
也就是说,其实注解并不是什么神秘的东西,注解就是用来简化我们开发的工具,而除了元注解之外,其余的注解都是属于自定义注解,例如@Override
就是一个自定义注解。
元注解
刚刚我们提到了元注解这个概念,我们不妨先来了解一下什么是元注解
简单来说,元注解就是(自定义)注解的注解。我个人理解的话,可以把元注解看成是自定义注解的元数据,用于标识这个(自定义)注解的属性。在JDK中,元注解一共只有以下4个:
注解名称 | 作用 |
---|---|
@Retention | 标识这个注解怎 么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问 |
@Documented | 标记这些注解是否包含在用户文档中。 |
@Target | 标记这个注解应该是哪种 Java 成员 |
@Inherited | 标记这个注解允许被继承(默认 注解并没有继承于任何子类) |
@Documented
注解相对比较简单,就不做过多解释;而@Inherited
一般使用在你希望使用自定义注解的类的子类可以继承这个注解,那么就可以使用该注解。下面我们主要针对@Retention
和@Target
这两个注解来做一个小案例进行演示:
自定义注解
需要注意的是,如果想要通过反射来获取注解,那么Retention对应的级别就需要设置成运行时可以访问
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DemoAnno {
String value() ;
}
在代码中进行测试
public class AnnoMainTest {
public static void main(String[] args) throws Exception{
getAnnoByReflex();
}
private static void getAnnoByReflex() throws Exception {
Class<?> aClass = Class.forName("com.xiaoming.annotation.User");
Method say = aClass.getDeclaredMethod("say");
DemoAnno declaredAnnotation = say.getDeclaredAnnotation(DemoAnno.class);
System.out.println(declaredAnnotation.value());
}
}
class User {
@DemoAnno("XiaoMing")
public String say(){
System.out.println("say some thing");
return null;
}
}
案例演示结果
看到这里,相信各位读者多多少少可以猜测出注解为什么需要配合反射来使用了, 我们定义好注解并且在类或者方法上使用后,后续就可以通过反射来获得哪个类/方法上到底有没有使用注解,如果使用了注解那么注解里面都有哪些配置。有了这些信息,一方面注解就起到了类似于XML的作用,另外一方面我们就可以将注解用来作为我们代码执行的依据。
具体如何作为我们代码执行的依据呢?我们可以看一下第三小节的案例,我们自定义一个类似于@Transactional
的事务注解。
三、自定义注解综合案例
对于使用过Spring框架的开发人员来说,想必对于@Transactional
注解肯定不陌生,我们常常在项目中使用该注解来实现声明式事务的效果。下面我们将手动实现一个注解,使得该自定义注解也有申明式事务的效果。
步骤一:创建基本类
实体类
public class User {
private Integer id;
private String name;
...
}
Mapper 类
public interface UserMapper {
@Select("select * from anno_test where id = #{id}")
List<User> getUserById(Integer id);
@Insert("insert into anno_test values(#{id},#{name})")
Integer saveUser(User user);
}
ServiceImpl类
public class UserServiceImpl implements UserService {
@Autowired
public UserMapper userMapper;
@Autowired
public TransactionUtil transactionUtil;
@Override
public List<User> getUserById(Integer id) {
return null;
}
@GetMapping("/saveUser")
@Override
public String saveUser() {
User user = new User(1, "xiaoming");
TransactionStatus transaction = null;
Integer result = userMapper.saveUser(user);
int i = 1 / 0;
return "SUCCESS";
}
}
在这种情况下,如果没有使用事务进行控制,那么在saveUser
方法被调用之后,即使因为除零异常报错,数据库中依然还是会插入一条数据。下面我们就来自定义一个注解来解决这个问题
步骤二:自定义MyTransactional
注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyTransactional {
}
步骤三:定义事务工具类和使用AOP进行切面拦截
这里需要注意,我们使用了@Arround
注解,表示将对有@MyTransactional
注解的方法进行拦截,而后进行环绕增强。可以看到,在环绕增强的方法中,我们使用了编程式事务进行了管理。
@Aspect
@Component
public class TransactionAop {
@Autowired
private TransactionUtil transactionUtil;
@Around(value = "@annotation(com.xiaoming.tranAnno.aop.MyTransactional)")
public Object around(ProceedingJoinPoint proceedingJoinPoint){
TransactionStatus transactionStatus = null;
try{
transactionStatus = transactionUtil.begin();
Object result = proceedingJoinPoint.proceed();
transactionUtil.commit(transactionStatus);
return result;
}catch (Throwable e){
e.printStackTrace();
if(transactionStatus != null){
transactionUtil.rollback(transactionStatus);
}
return "FAIL";
}
}
}
步骤四:使用我们的自定义注解:
在我们想要进行事务管理的方法上添加上我们的自定义注解
/**
* 下面这种做法为使用自定义注解的做法
* @return
*/
@GetMapping("/saveUser")
@MyTransactional
@Override
public String saveUser() {
User user = new User(1, "xiaoming");
TransactionStatus transaction = null;
Integer result = userMapper.saveUser(user);
int i = 1 / 0;
return "SUCCESS";
}
运行代码,数据库没有新增数据
综上,我们可以知道其实注解并不是一个很神秘的东西,本质上就是一个简化我们开发的工具。而这个工具想要真正生效的话还需要依托反射技术(让我们可以知道哪些类/方法上面使用了我们的自定义注解)和AOP技术(拦截到使用了自定义注解的方法 / 类之后进行选择性的增强)。
本篇文章所使用到的完整代码可以从我的仓库中进行下载:https://gitee.com/moutory/custom_annotation
网友评论