package handler;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
/\*\*
\* @author lscl
\* @version 1.0
\* @intro: 缓存处理器: 当执行query方法时存入缓存;执行save/update/delete方法时清除缓存;
\*/
public class CacheHandler implements InvocationHandler {
// 目标对象
private Object target;
public CacheHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 执行目标方法并获取目标方法的返回值
Object returnVal = method.invoke(target, args);
// 获取要执行的方法名称
String methodName = method.getName();
if (
"save".equals(methodName) ||
"update".equals(methodName) ||
"delete".equals(methodName)
) {
System.out.println("清除缓存成功...");
} else if ("query".equals(methodName)) {
System.out.println("存入缓存成功...");
}
return returnVal;
}
}
1.2.4 测试类
@Test
public void test2() {
// 针对日志功能的增强(具备日志功能)
UserDaoInterface userDaoLoggerProxy = (UserDaoInterface) Proxy.newProxyInstance(
Demo01.class.getClassLoader(),
UserDao.class.getInterfaces(),
new LoggerHandler(new UserDao())
);
// 具备日志功能,不具备缓存功能
userDaoLoggerProxy.query();
System.out.println("-----------------");
// 针对缓存功能的增强
UserDaoInterface userDaoCacheProxy = (UserDaoInterface) Proxy.newProxyInstance(
Demo01.class.getClassLoader(),
UserDao.class.getInterfaces(),
new CacheHandler(userDaoLoggerProxy) // 传入已经进行日志增强的代理类,继续代理加强缓存相关功能
);
// 具备日志和缓存功能
userDaoCacheProxy.query();
}
执行效果:
二、AOP
2.1 AOP 概述
AOP(Aspect Oriented Programming),即面向切面编程。通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件系统开发中的一个热点,也是Spring框架的一个重点。利用AOP可以实现业务逻辑各个部分的隔离,从而使得业务逻辑各个部分的耦合性降低,提高程序的可重用性,同时提高开发效率。
2.1.1 纵向编程
2.1.2 纵横配合的编程
面向切面的意思简单来说就是:纵向重复,横向抽取,在我们的业务中间切一刀来填充我们的增强代码;
AOP编程操作的主要对象是切面(aspect),而切面模块化横切关注点(需要增强的方法)。
2.2 AOP相关语术
2.2.1 Joinpoint(连接点):
- 连接点:在程序执行过程中,需要拦截的方法;
- 根据规则,可以指定拦截的方法,我们将每一个被拦截的方法称为连接点。如:UserService中的save()方法就是连接点
2.2.2 Pointcut(切入点):
- 切入点:就是拦截方法设置的规则,连接点的一系列集合
- 所谓切入点是指我们要对哪些Joinpoint进行拦截的定义,比如我们刚刚的的案例中,save/delete/update/query这些都是我们需要增强的方法,这些方法也是我们刚刚说的连接点,我们需要通过一个表达式把这些需要增强的方法找出来,通过这个表达式查找出来的一批方法就称为切入点;
2.2.3 Advice(通知):
- 通知:增强连接点的实现代码(就是需要为连接点绑定的方法)
- 所谓通知是指拦截到Joinpoint之后所要做的事情(增强的代码)就是通知
由于执行代码的时机不同,比如我们刚刚的日志增强代码在执行目标对象方法前后都需要执行,缓存代码则是在执行目标对象之后才执行。因此通知也被化分为了很多种类型:
- 前置通知(Before):执行连接点方法之前执行
- 环绕通知(Around):环绕连接点方法执行
- 后置通知(After):执行连接点方法之后执行
- 返回通知(After Running):在连接点方法返回结果之后执行,如果方法出现异常则不会执行此通知(通常是最后执行)
- 异常通知(After Throwing):在连接点方法抛出异常之后执行
2.2.4 target(目标对象):
- 目标对象:被代理对象,连接点的所属方法,也就是需要增强的方法
2.2.5 Weaving(织入):
- 织入:与前面的语术不一样,织入是个动词,指的是将通知(增强的代码)应用到切入点(需要增强的方法)形成切面的过程,在具体应用中作用不大,我们了解即可;
2.2.6 Proxy(代理):
- 代理:一个类被AOP织入增强后,就产生一个结果代理类
2.2.7 Aspect(切面):
- 切面:我们的拦截处理类;由于处理类里面需要执行所有拦截到的目标方法,也就是切入点,还包括我们增强的功能代码,也就是通知,因此我们一般称切面(Aspect)=切入点(Pointcut)+通知(Advice)
三、Spring中的AOP
3.1 AspectJ
AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,是Java社区中最完整最流行的AOP框架
在Spring2.0以上版本中,可以使用基于AspectJ注解或基于XML配置的AOP。
Tips:我们本次课程并不是使用原生的AspectJ,而是使用Spring对AspectJ的封装
3.2 切入点表达式
切入点就是能够增强的方法的一系列集合,我们要通过表达式的方式定位一个或多个具体的连接点,这些所有的连接点被称为切入点;
- 语法:
execution( [权限修饰符] [返回值类型] [简单类名/全类名] [方法名] ([参数列表]) )
示例一:
execution(\* com.dfbz.dao.UserDao.*(..))
说明: com.dfbz.dao包下的UserDao接口中声明的所有方法
// 解释:任意权限修饰符、任意方法返回值、com.dfbz.dao.UserDao接口、任意方法名、任意参数列表
第一个"\*"代表任意修饰符及任意返回值。
第二个"\*"代表任意方法。
".."匹配任意数量、任意类型的参数。
若目标类、接口与该切面类在同一个包中可以省略包名。
示例二:
execution(public \* UserDao.*(..))
说明: UserDao接口的所有公有方法
// 必须public修饰、任意返回值、UserDao接口、任意方法名、任意参数列表
示例三:
execution(public String UserDao.*(..))
说明: UserDao中所有返回值为String类型的共有方法方法
// 必须public修饰、方法返回值必须String、UserDao接口(一级目录)、任意方法名、任意参数列表
示例四:
execution(private \* UserDao.*(Integer, ..))
说明: 第一个参数为Integer类型的private修饰的方法。
// 必须是private修饰、任意方法返回值、UserDao接口(一级目录)、任意方法名、第一个参数必须是Integer,后面可以匹配任意参数(包含0个)
".." 匹配任意数量、任意类型的参数。
示例五:
execution(public double UserDao.*(double, double))
说明: 修饰符为public,返回值为double,UserDao中的两个参数都为double的方法
// 必须是public修饰符、返回值类型是double、UserDao接口(一级目录)、任意方法名、第一个参数为double、第二个参数为double
示例六:
execution(public void \*.UserDao.*(..))
说明: 修饰符为public,返回值为void,任意一级包下的UserDao的任意方法
// 必须是publ