Spring-AOP
目录
Spring-AOP
Spring-AOP
日志的两种编码方式
硬编码
- 硬编码是指在代码中 直接写死 具体的实现逻辑,没有使用任何代理模式或额外的抽象层。 当需要修改逻辑时,必须直接修改代码, 可维护性差,扩展性低 。
静态代码:不修改源代码 低耦合
- 定义: 创建一个代理对象,包装这个组件.以后业务的执行从代理对象出发不直接调用组件
- 特点: 定义期间就指定好了互相代理关系
动态代理:
- 动态代理在 运行时生成代理类 ,无需手动编写代理类,适用于需要动态扩展功能的场景。
动态代理具体实现
1.创建目标对象接口
public interface UserService {
void saveUser();
}
2.创建实现类实现目标对象的方法
public class UserServiceImpl implements UserService {
@Override
public void saveUser() {
System.out.println("UserServiceImpl.saveUser");
}
}
3.1创建动态代理工厂
public class DynamicProxy {
public static<T> T getProxyInstance(T target) {
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(proxy, method, args) -> {
String name = method.getName();
LogUtil.logStart(name,args);
Object result = null;
try {
result = method.invoke(target, args);
LogUtil.logReturn(name, result);
}catch (Exception e){
LogUtil.logException(name,e);
}finally {
LogUtil.LogEnd(name);
}
return result;
});
3.2使用@Aspect注解(声明切入点表达式)
/**
@Before: 前置通知在方法运行前
@AfterReturning : 方法执行正常返回
@AfterThrowing :方法抛出异常运行
@After: 方法执行之后运行
@Around: 环绕通知
*/
@Pointcut("execution(int org.example.spring02aop.calculator.impl.MathCalculatorImpl.add(int,int))")
@Before("execution(int *(int,int))")
public void logStart(){
System.out.println("LogStart");
}
@After("execution(int *(int,int))")
public void logEnd(){
System.out.println("logEnd");
}
@AfterReturning("execution(int *(int,int))")
public void logReturn(){
System.out.println("logEnd");
}
@AfterThrowing("execution(int *(int,int))")
public void logException(){
System.out.println("logException");
}
3.3环绕通知
@Aspect
@Component
public class AroundAspect {
@Pointcut("execution(int org.example.spring02aop.calculator.impl.MathCalculatorImpl.add(int,int))")
public void pointcut(){}
@Around("pointcut()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint)throws Throwable{
Object[] args = joinPoint.getArgs();//获取目标方法的参数
System.out.println("环绕前置通知,args:"+ Arrays.toString(args));
Object proceed = null;
try {
proceed = joinPoint.proceed();//继续执行目标方法`;反射 method.invoke();
System.out.println("环绕返回通知,返回值:"+proceed);
}catch (Exception e) {
System.out.println("环绕异常通知"+e.getMessage());
}finally {
System.out.println("环绕后置通知,done");
}
return proceed;
}
}
总结 :若要统一日志管理,推荐使用环绕通知,代码入侵性低,复杂度低
切入点表达式的细节
规则 | 示例 | 说明 |
---|---|---|
匹配所有方法 | execution(* *(..)) | 任意类的任意方法 |
匹配某个类的所有方法 | execution(* com.example.service.UserService.*(..)) | UserService 的所有方法 |
匹配某个包下的所有方法 | execution(* com.example.service.*.*(..)) | service 包下所有类的方法 |
匹配某个包及子包下的所有方法 | execution(* com.example.service..*.*(..)) | service 及其子包下的所有方法 |
匹配特定方法 | execution(* com.example.service.UserService.getUser(..)) | getUser 方法 |
匹配某个返回类型的方法 | execution(String com.example.service.UserService.*(..)) | UserService 中返回 String 的方法 |
匹配某些参数的方法 | execution(* com.example.service.UserService.*(String)) | 只有 一个 String 参数 的方法 |
上面例子写的就是只有名为add接收参数为两个int的方法才能触发此日志执行