定义
AOP(Aspect Orenited Programming : 面向切面(方面)编程)是OOP的补充
把多个类中共有的代码,抽取到一个类中成为切面类(此类用@Abpect修饰),抽取出来的代码成为切面,通过切入点表达式切入到指定的类中,用于实现功能的扩展,将目标代码(业务代码)和扩展代码(非业务代码)分离 ,降低耦合性。 比如 每个方法 打新日志,service层使用的事物就是通过aop实现的
AOP相关概念
1.横切关注点
目标方法中执行的扩展代码(例如:日志 事物处理等),将这些代码从业务代码中分离出去,形成一个个通知
2.切面类
用@Aspect注解,独立地进行功能扩展的类,包含通知方法
3.通知
前置通知:@Before 在目标方法执行前进行功能扩展
返回通知:@AfterRunning 在目标方法后执行功能的扩展
异常通知:@AfterThrowing 目标方法出现异常才执行功能的扩展
最终(后置)通知:After 目标方法中不管是否出现异常都要执行功能的扩展
环绕通知:@Around 相当于前四种通知的组合使用,类似于拦截器,类似于InvocationHandler的invoke()方法
4.连接点
连接点就i是目标方法需要执行扩展的位置,一般连接点就i是一个方法(目标方法),在这个方法前后等完成功能的扩展
5.切入点
切入点需要通过切入点表达式来查找的,找到的可能是一个或多个连接点
一、基于注解的AOP
启用基于注解AOP功能
准备目标类接口和目标类
//目标接口
public interface MathCalculator {
int add( int i,int j);
int sub( int i,int j);
}
//目标类:目标对象(编写业务代码)
@Component
public class EazyImpl implements MathCalculator{
//目标对象中的方法,可以看做是连接点(实施扩展的代码位置)
public int add(int i, int j) {
int result = i + j;
System. out.println("方法内部打印:result=" +result);
return result;
}
public int sub(int i, int j) {
int result = i - j;
System. out.println("方法内部打印:result=" +result);
return result;
}
}
切面类:完成日志功能扩展。 一个切面类中,应该包含多个通知的方法。通知方法中编写的是非业务代码
@Component
@Aspect
@Order(value = 10) //多个切面执行扩展时,使用@Order注解的value来指定执行顺序,值越小,优先级越高
public class LogAspect {
// 可以被重复利用
@Pointcut(value = "execution(* *.*(..))")
private void pointcut() {}
// 前置通知
// value值:需要设置一个切入点表达式,将连接点(目标方法)找到,在连接点上执行前置通知。
// @Before(value="execution(* *.*(..))")
@Before(value = "pointcut()")
public void startLog(JoinPoint joinPoint) {
// 通过JoinPoint(切入点对象)获取方法参数列表 为数组
Object[] args = joinPoint.getArgs();
// 将数组转化为集合
List
测试
@Test
public void test() {
//获取代理对象
//目标类有接口:那么,框架采用的是JDK动态代理来进行功能扩展的。
MathCalculator obj = ioc.getBean(MathCalculator.class);
//目标类没有实现任何接口:那么,框架采用的是 Cglib动态代理来进行功能扩展的。
//EazyImpl obj = ioc.getBean(EazyImpl.class);
//目标类如果实现某个特定的接口,那么,就会产生JDK动态代理,代理类不是目标类的子类,所以,强制转换存在异常。
//EazyImpl obj = (EazyImpl)ioc.getBean("eazyImpl");
System. out.println(obj.getClass());
obj.add(10, 5);
}
二、基于XMl的AOP
以AOP技术为基础,记录项目中的异常日志信息
public class LogRecordor {
@Autowired
private LogService logService;
// 记录日志的通知方法,环绕通知
public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取方法签名
Signature signature = joinPoint.getSignature();
// 通过方法签名获取方法名
String methodName = signature.getName();
//通过方法签名获取方法所在的类的类型
String className = signature.getDeclaringTypeName();
//获取目标方法执行时的参数的数组
Object[] args = joinPoint.getArgs();
Object result = null;
String exceptionType = null;
String exceptionMessage = null;
try{
//调用目标方法
joinPoint.proceed();
}catch(Throwable e){
//获取异常信息的初始化操作
exceptionType = e.getClass().getName();
exceptionMessage = e.getMessage();
//尝试获取当前异常的原因:原因可能有也可能没有
Throwable cause = e.getCause();
while(cause!=null){
//如果有则,找到更深一层的原因
exceptionType = cause.getClass().getName();
exceptionMessage = cause.getMessage();
//进一步获取,原因的原因,知道最底层的原因位置,才能结束while循环
cause = cause.getCause();
}
//这里收集异常信息后,还需要再抛出,否则框架将捕获不到异常,声明式异常将不起作用
throw e;
}finally{
//为记录日志收集信息并将其保存到数据库
//操作时间(对时间进行格式化)
String operateTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());;
//将参数的数组转化为list以便于打印toString();
List argList = Arrays.asList(args);
//返回值
String returnValue = (result==null)?"【无】":result.toString();
//从当前线程获取request对象(在这里不能注入requset对象因为request对象不是有IOC创建的,
//也不能new HttpServletRequest(),因为本身要的是request是用户的请求里面有请求参数,如果new一个新的request对象的话是一个新的request空对象)
HttpServletRequest request = RequestThreadBinder.getRequest();
HttpSession session = request.getSession();
User user = (User) session.getAttribute(GlobalNames.LOGIN_USER);
Admin admin = (Admin) session.getAttribute(GlobalNames.LOGIN_ADMIN);
String operator = ((user==null)?"":user.getUserName())+"/"+((admin==null)?"":admin.getAdminName());
Log log = new Log(null, operateTime, operator, methodName, className, argList.toString(), returnValue, exceptionType,exceptionMessage);
//绑定数据源对应的键以便选择数据源
RouterToken.bindToken(RouterToken.DATASOURCE_LOG);
//专门定义一个方法,将日志信息保存到‘当月的’数据表中
logService.saveLog(log);
}
return result;
}
}
配置切面日志类bean
s
线程本地化-ThreadLocal的使用
(某个方法需要使用HttpServletRequest,但是方法中没有传入当前的request对象)
自定义线程本地化绑定的类
public class RequestThreadBinder {
// 声明当前线程 指定泛型为request
private static ThreadLocal local = new ThreadLocal();
// 将request绑定到当前线程的方法
public static void bindRequest(HttpServletRequest request) {
local.set(request);
}
// 从当前线程获取request的方法
public static HttpServletRequest getRequest() {
return local.get();
}
// 从当前线程移除request的方法
public static void removeRequest() {
local.remove();
}
}
自定义拦截器(可考虑重新参考Spring-MVC的自定义拦截器),将HttpServletRequest绑定到本地线程上
public class RequestBindingInterceptor extends HandlerInterceptorAdapter{
@Override
public boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler) sthrows Exception {
//在调用目标handler方法之前将request对象绑定到当前线程
RequestThreadBinder.bindRequest(request);
return true;
}
@Override
public void postHandle(HttpServletRequest request,HttpServletResponse response, Object handler,ModelAndView modelAndView) throws Exception {
//在执行完目标handler方法之后将request对象从当前线程移除
RequestThreadBinder.removeRequest();
}
}