基于annotation的AOP
Spring使用的AOP注解分为三个层次:
1、@Aspect放在类头上,把这个类作为一个切面。
2、@Pointcut放在方法头上,定义一个可被别的方法引用的切入点表达式。
3、5种通知
- @Before,前置通知,放在方法头上。
- @After,后置【finally】通知,放在方法头上。
- @AfterReturning,后置【try】通知,放在方法头上,使用returning来引用方法返回值,即可以访问到方法的返回值。
- @AfterThrowing,后置【catch】通知,也叫抛出通知,放在方法头上,可以访问到异常对象,可以指定在出现特定异常时在执行通知代码。
- @Around,环绕通知,放在方法头上,这个方法要决定真实的方法是否执行,而且必须有返回值。
要想实现基于annotation的AOP,首先应该在配置文件打开annotation AOP的支持。配置如下:
然后我们就可以在代码中使用AOP的annotation:
@Component
@Aspect //声明这个类是一个切面类
public class LogAspect {
/**
* 定义Pointcut(在哪里做的集合)
* 一个Pointcut定义由Pointcut表示式和Pointcut签名组成
*/
//Pointcut表示式
@Pointcut("execution(public * com.service.impl..*.*(..))")
//Point签名,此方法不能有返回值,该方法只是一个标示 。
public void recordLog() {}
@AfterReturning(pointcut = "recordLog()") //引用命名切入点
public void simpleAdvice() {
LogUtil.info("AOP后处理成功");
}
@Around("recordLog()")
public void aroundLogCalls(ProceedingJoinPoint jp) throws Throwable {
LogUtil.info("正常运行");
jp.proceed(); //执行程序
LogUtil.info("运行结束");
}
@Before("recordLog()")
//如果希望获取相应的调用信息,可以通过JoinPoint这个参数进行传递
public void before(JoinPoint jp) {
String className = jp.getThis().toString();
String methodName = jp.getSignature().getName(); // 获得方法名
LogUtil.info("位于:" + className + "调用" + methodName + "()方法-开始!");
Object[] args = jp.getArgs(); // 获得参数列表
if (args.length <= 0) {
LogUtil.info("====" + methodName + "方法没有参数");
} else {
for (int i = 0; i < args.length; i++) {
LogUtil.info("====参数 " + (i + 1) + ":" + args[i]);
}
}
LogUtil.info("=====================================");
}
@AfterThrowing("recordLog()")
public void catchInfo() {
LogUtil.info("异常信息");
}
@After("recordLog()")
public void after(JoinPoint jp) {
LogUtil.info("" + jp.getSignature().getName() + "()方法-结束!");
LogUtil.info("=====================================");
}
}
我们也可以直接在@Before、@Around中定义切点:
@Component("logAspect") //让这个切面类被spring所管理
@Aspect //声明这个类是一个切面类
public class LogAspect{
@Before("execution(* org.zyt.init.spring.dao.*.add*(..))||"+
"execution(* org.zyt.init.spring.dao.*.delete*(..))||")
public void logStart(){
Logger.info("加入日志");
}
}
带参数的Pointcut
如果只要访问目标方法的参数,spring还提供了一种更简单的方法:我们可以在程序中使用args来绑定目标方法的参数。如果在一个args表达式中指定了一个或多个参数,则该切入点将只匹配具有对应形参的方法,且目标方法的参数值将被传入增强处理方法。下面以一个例子说明。
【示例】
UserController.java
@Controller
@RequestMapping("/user")
public class UserController {
@Resource
public UserService userService;
@RequestMapping(value="/login", method= RequestMethod.POST)
public String login(@RequestParam(required = true) String userName,
@RequestParam(required = true) String password){
String result = "login successfully!";
return result;
}
}
verifyUserAspect.java
@Aspect
@Component
public class verifyUserAspect{
@Resource
public UserDao userDao;
@Resource
public LogService logService;
//可以通过“argNames”属性指定参数名
@Pointcut("execution(public * com.AOPExercise.controller.UserController.*(String,String)) && args(userName,password)")
public void userPointcut(String userName,String password){}
@Around(value="userPointcut(userName,password)")
public Object verifyUser(ProceedingJoinPoint pjp, String userName, String password) throws Throwable {
Object result = null;
User u = userDao.findUserByName(userName);
if(u==null){
result="login invalid!";
}else{
if(!u.getPassword().equals(password)){
result="login invalid!";
System.out.println("验证不合法1");
}else{
//记录用户登录日志
SimpleDateFormat sdFormatter = new SimpleDateFormat("yyyy-MM-dd");
String retStrFormatNowDate = sdFormatter.format(new Date(System.currentTimeMillis()));
String message = "用户"+userName+"在"+retStrFormatNowDate+"登录";
logService.addLog(message);
//执行login()方法
result = pjp.proceed();
}
}
return result;
}
}
请求结果
传输错误的密码结果如下:
传输正确的密码结果如下:
基于xml的AOP
beans.xml中AOP的配置:
//切面代码
【说明】
(1)aop:pointcut标签配置切点,表示哪些位置要使用增强。
由于是在Service中进行数据库业务操作,配的应该是包含那些作为事务的方法的Service类。首先应该特别注意的是id的命名,同样由于每个模块都有自己事务切面,所以我觉得初步的命名规则因为 all+模块名+ServiceMethod。而且每个模块之间不同之处还在于以下一句:
expression="execution(...)"
织入点语法
1、 无返回值、com.zoy.dao.UserDaoImpl.save方法、参数为User
execution(public void com.zoy.dao.UserDaoImpl.save(com.model.User))
2、 任何包、任何类、任何返回值、任何方法的任何参数
execution(public * *(..))
3、 任何包、任何类、任何返回值、任何set开头方法的任何参数
//第一个*表示任意返回值
execution(* set*(..))
4、 任何返回值、com.zoy.service.AccountService类中的任何方法、任何参数
execution(* com.zoy.service.AccountService.*(..))
5、 任何返回值、com.zoy.service包中任何类中的任何方法、任何参数
execution(* com.zoy.service.*.*(..))
6、 任何返回值、com.zoy.service包中任何层次子包(..)、任何类、任何方法、任何参数
execution(* com.zoy.service..*.*(..))
7、 void 和 !void(非void)
execution(public void com.zoy.service..*.*(..))
execution(public !void com.zoy.service..*.*(..))
aop:pointcut标签也可以为pointcut配置参数。如下例所示:
(2)
我们也可以直接在
如果在aop:pointcut标签中配置了参数,且增强方法想要使用其中的参数的话,则用arg-names属性进行配置:
(3)aop:advisor 与 aop:aspect的区别
在面向切面编程时,我们会使用< aop:aspect>;在进行事务管理时,我们会使用< aop:advisor>。那么,对于< aop:aspect>与< aop:advisor>的区别,具体是怎样的呢?
其实Adivisor只持有一个Pointcut和一个advice,而Aspect可以多个Pointcut和多个advice,所以Adivisor是一种特殊的Aspect。
1、实现方式不同
< aop:aspect>定义切面时,只需要定义一般的bean就行,而定义< aop:advisor>中引用的通知时,通知必须实现Advice接口。下面我们举例说明。
首先,我们定义一个接口Sleepable和这个接口的实现Human,代码如下:
public interface Sleepable {
public void sleep();
}
public class Human implements Sleepable {
@Override
public void sleep() {
System.out.println("我要睡觉了!");
}
}
下面是< aop:advisor>的实现方式:
//定义通知
public class SleepHelper implements MethodBeforeAdvice,AfterReturningAdvice{
@Override
public void before(Method arg0, Object[] arg1, Object arg2)
throws Throwable {
System.out.println("睡觉前要脱衣服!");
}
@Override
public void afterReturning(Object arg0, Method arg1, Object[] arg2,
Object arg3) throws Throwable {
System.out.println("起床后要穿衣服!");
}
}
//aop配置
下面是< aop:aspect>的实现方式:
//定义切面
public class SleepHelper2{
public void beforeSleep(){
System.out.println("睡觉前要脱衣服!");
}
public void afterSleep(){
System.out.println("起床后要穿衣服!");
}
}
//aop配置
测试代码如下:
public class TestAOP {
public static void main(String[] args) {
method1();
// method2();
}
private static void method1() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext1.xml");
Sleepable sleeper = (Sleepable) context.getBean("human");
sleeper.sleep();
}
private static void method2() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext2.xml");
Sleepable sleeper = (Sleepable) context.getBean("human");
sleeper.sleep();
}
}
2、使用场景不同
从上述中可知,aop:advisor标签中的advice-ref属性可以指向一个切面实现的bean,也可以指向一个
当advice-ref属性指向一个切面实现的bean时,配置的是切面实现的增强;当advice-ref属性指向一个
< aop:aspect>大多用于日志,缓存。