一、java注解简介
从JDK5开始,Java增加对元数据的支持,也就是注解,注解与注释是有一定区别的,可以把注解理解为代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过注解开发人员可以在不改变原有代码和逻辑的情况下在源代码中嵌入补充信息。
二、自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Iredis {
String prefix();
long timeout();
TimeUnit timeUnit();
}
三、4种标准元数据
1.@Target
@Target用于描述注解的使用范围,也就是说使用了@Target去定义一个注解,那么可以决定定义好的注解能用在什么地方可以用ElementType枚举类来指定范围
2.@Retention
@Retention用于描述注解的生命周期,也就是说这个注解在什么范围内有效,注解的生命周期和三个阶段有关:源代码阶段(SOURCE)、class文件中有效(CLASS)、运行时有效(RUNTIME),
3.@Documented
@Documented表示该注解是否可以生成到API文档中。
4.@Inherited
@Inherited是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个Annotation将被用于该class的子类。
四、自定义注解在Spring中的应用
在这之前需要普及一些Spring的注解
@Component:把普通pojo实例化到spring容器中
@Aspect:把当前类标识为一个切面供容器读取
@Before:前置通知,在方法执行之前执行
@After:后置通知,在方法执行之后执行
@Around:环绕通知,围绕着方法执行
@AfterReturning:返回通知,在方法返回结果之后执行
@AfterThrowing:异常通知,在方法抛出异常之后执行
注解方式应用切面类
1.创建切面类
@Component
@Aspect
public class RedisAspect {
/**
* 环绕切面
* @annotation:用于匹配当前执行方法持有指定注解的方法
* @param point
* @return
* @throws Throwable
*/
@Around("@annotation(com.test.annotation.Iredis)")
public Object around(ProceedingJoinPoint point) throws Throwable {
System.out.println("hello");
//point.proceed();方法是用来执行注解标注方法的
Object data = point.proceed(point.getArgs());
return data;
}
}
环绕通知注意地方:
1. 环绕通知的参数类型必须是ProceedingJoinPoint类型的,他是JoinPoint的子接口,可以控制何时执行连接点
2. 在环绕通知中必须使用ProceedingJoinPoint的proceed()方法来手动执行被代理的方法
3. 环绕通知的方法需要返回proceed()方法执行后的结果,否则会出现空指针异常
2.创建连接点
在连接点上加入自定义注解就通过注解来应用切面类了
这里的方法是根据id查询用户的一个方法
@Iredis(prefix = "ss" , timeout = 1L , timeUnit = TimeUnit.SECONDS)
@Override
public Admin getAdmin(Integer id) {
Admin admin = adminMapper.selectAdminById(id);
return admin;
}
3.创建测试类验证
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-beans.xml" , "classpath:spring-mybatis.xml"})
public class AOPtest {
@Autowired
AdminService adminService;
@Test
public void test01() {
System.out.println(adminService.getAdmin(1));
}
}
返回结果
hello
Admin{id=1, name='lzy', sex=1, age=20, desc='sdsdfs'}
五、通过注解引入redis缓存
直接代码
@Slf4j
@Component
@Aspect
public class RedisAspect {
@Resource
StringRedisTemplate redisTemplate;
/**
* 环绕切面
* @annotation:用于匹配当前执行方法持有指定注解的方法
* @param point
* @return
* @throws Throwable
*/
@Around("@annotation(com.test.annotation.Iredis)")
public Object around(ProceedingJoinPoint point) throws Throwable {
Object data = null;
try {
log.info("前置通知");
//获取连接点参数
Object[] args = point.getArgs();
//获取连接点方法
MethodSignature signature = (MethodSignature) point.getSignature();
//获取方法上的注解
Iredis annotation = signature.getMethod().getAnnotation(Iredis.class);
//如果没有获取到注解直接返回方法执行结果
if (annotation == null){
return point.proceed(args);
}
//获取注解prefix值(redis的前缀名)
String prefix = annotation.prefix();
//将前缀名和参数进行累加,参数是具体的ID值
if (args!=null){
for (Object arg : args) {
prefix+=arg.toString();
}
}
//先进行查找判断redis里是否有这条数据,如果有就直接返回redis里的数据
Object redisData = getRedisData(prefix , signature);
if (null!=redisData){
return redisData;
}else {
//没有在缓存中查到数据,需要将数据存入缓存
Lock lock = new ReentrantLock();
lock.lock();
try
{
//进行第二次判断
if (null == redisData){
data = point.proceed(args);
Gson gson = new Gson();
//将数据以json字符串形式存入redis,redis数据类型为String类型
redisTemplate.opsForValue().set(prefix , gson.toJson(data) , annotation.timeout() , annotation.timeUnit());
redisData=data;
}
return redisData;
}catch (Exception e){
//e.printStackTrace();
}finally {
lock.unlock();
}
}
}catch (Exception e){
log.info("异常通知"+e);
}finally {
log.info("后置通知");
}
return null;
}
public Object getRedisData(String prefix , MethodSignature signature){
String s = redisTemplate.opsForValue().get(prefix);
//判断是否查询出数据,redis返回的数据是JSON格式的字符串
if (null!=s){
//获取连接点返回值类型,并将json格式字符串转成对应的类型返回
Class returnType = signature.getReturnType();
Gson gson = new Gson();
return gson.fromJson(s, returnType);
}
return null;
}
}
如果使用ssm框架出现获取不到注解的情况需要在spring配置文件里面加入
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<aop:aspectj-autoproxy proxy-target-class="true"/>