4.利用AOP实现自定义Spring注解
AOP基本概念
- 切面(
Aspect
) :通知和切入点共同组成了切面,时间、地点和要发生的“故事”。 - 连接点(
Joinpoint
) :程序能够应用通知的一个“时机”,这些“时机”就是连接点,例如方法被调用时、异常被抛出时等等。 - 通知(
Advice
) :通知定义了切面是什么以及何时使用。描述了切面要完成的工作和何时需要执行这个工作。 - 切入点(
Pointcut
) :通知定义了切面要发生的“故事”和时间,那么切入点就定义了“故事”发生的地点,例如某个类或方法的名称。 - 目标对象(
Target Object
) :即被通知的对象。
AOP代理(AOP Proxy) 在Spring AOP中有两种代理方式,JDK动态代理和CGLIB代理。默认情况下,TargetObject实现了接口时,则采用JDK动态代理;反之,采用CGLIB代理。
织入(Weaving)把切面应用到目标对象来创建新的代理对象的过程,织入一般发生在如下几个时机:
1)编译时:当一个类文件被编译时进行织入,这需要特殊的编译器才能做到,例如AspectJ的织入编译器;
2)类加载时:使用特殊的ClassLoader在目标类被加载到程序之前增强类的字节代码;
3)运行时:切面在运行的某个时刻被织入,SpringAOP就是以这种方式织入切面的,原理是使用了JDK的动态代理。
AOP通知类型
@Before
前置通知(Before advice) :在某连接点(JoinPoint
)之前执行的通知,但这个通知不能阻止连接点前的执行。
@After
后通知(After advice) :当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
@AfterReturning
返回后通知(After return advice) :在某连接点正常完成后执行的通知,不包括抛出异常的情况。
@Around
环绕通知(Around advice) :包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。
@AfterThrowing
抛出异常后通知(After throwing advice) : 在方法抛出异常退出时执行的通知。
自定义Spring注解实现
定义注解有好几种实现方式,有使用aop实现的,也有使用拦截器实现的,实现的话有可能各自有的优缺点,不过总的来说,还是推荐使用aop方式实现。
定义注解类
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.itaem.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 说明:
* 定义日志注解
*
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FileLog {
String value() default "日志记录开始";
}
定义注解的处理程序
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.itaem.aspect;
import net.itaem.annotation.FileLog;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* 说明:
* 日志的切面处理程序
*
*/
@Aspect
@Component
public class LogAspect {
@AfterReturning("within(net.itaem..*) && @annotation(fl)")
public void addSuccessLog(JoinPoint jp, FileLog fl){
Object[] parames = jp.getArgs();
//获取目标方法体参数
String className = jp.getTarget().getClass().toString();
//获取目标类名
String signature = jp.getSignature().toString();
//获取目标方法签名
String methodName = signature.substring(signature.lastIndexOf(".")+1, signature.indexOf("("));
//获取注解值
String desc=fl.value();
//把调用的信息写到日常记录信息里面去...
}
//标注该方法体为异常通知,当目标方法出现异常时,执行该方法体
@AfterThrowing(pointcut="within(net.itaem..*) && @annotation(fl)", throwing="e")
public void addExceptionLog(JoinPoint jp, FileLog fl, Exception e){
//把错误信息写到错误日志文件里面去...
}
}
编写测试用例
配置spring启动xml,配置如下
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd ">
<aop:aspectj-autoproxy/>
<bean name="purchaseService" class="net.itaem.test.PurchaseService" />
<bean class="net.itaem.aspect.LogAspect"/>
</beans>
编写service类
public class PurchaseService {
public void purchaseProduct(String productName,int price,String type) {
System.out.println("购买商品。。。");
}
}
编写测试代码
package net.itaem.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
@org.junit.Test
public void test() {
ApplicationContext context=new ClassPathXmlApplicationContext("net/itaem/source/test.xml");
PurchaseService service=(PurchaseService) context.getBean("purchaseService");
service.purchaseProduct("电风扇", 98, "日用品");
}
}
结果展示
image.png总结
个人觉得这个实现起来比较简答, 也比较容易理解,当然其他的更深入的扩展,还是要慢慢的去学习,探索,这里只是简单的做了一个实现注解的方式介绍。其实学习的这种状态挺快乐的,无形中有一种自己又升华了的感觉,好好加油吧~