直观上的感受就是,在执行原方法的同时,在原方法的前后切入了另一些方法
Spring AOP基于动态代理,如果代理对象有接口,使用jdk动态代理;如果没有,会使用cglib
那AOP有什么好处呢?它将一些类似日志操作等大量在项目中重复的代码独立出来,降低模块间的耦合度,有利于未来的可拓展性和可维护性
静态代理:每一个方法都需要写一个代理方法,可以通过Impl或者子类实现
动态代理:所有方法都可以公用一个代理方法,通过java给定的类实现
比如,现在我有一个User接口,有一个UserImpl的实现
public interface User {
public void name(String str);
}
public class UserImpl implements User {
@Override
public void name(String str) {
System.out.println(str);
}
}
现在我想在每一个name方法调用后执行一条语句
如果是静态代理的方式,需要使用类似装饰器模式的操作,使用一个类来继承它
public class StaticProxyUser extends UserImpl {
@Override
public void name(String str) {
super.name(str);
System.out.println("代理语句执行");
}
}
像这么写的话代码的可重用性不高,如果其他接口也像使用代理每个接口都需要写这样一段代码
被代理的类实现了接口的时候才能使用,生成的代理类是实现了相同接口的同级代理类
通过Proxy的newProxyInstance生成代理类,这个代理类的方法都会视为代理增强后的方法
class invocation implements InvocationHandler{
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//重写这个方法,然后把这个类传入newProxyInstance的第三个参数中
return null;
}
}
以下是代理类
public class JDKProxyUser implements InvocationHandler {
User user;
public JDKProxyUser(User user) {
this.user = user;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
method.invoke(user, args);
System.out.println("代理语句执行");
return null;
}
}
主类创建
@Test
void proxyTest() {
User user = new UserImpl();
Class[] classes = {User.class};
User user1 = (User) Proxy.newProxyInstance(User.class.getClassLoader(), classes, new JDKProxyUser(user));
user1.name("yifanxie");
}
可以看见这时候的user可以传入任何实现类对象,不必再一个个去写子类实现了,这样程序的可扩展性就大大增加了
调用这个代理类的方法其实是调用了(事实上我们也重写了)InvocationHandler的invoke方法(Invocation:求助)
被代理的类是代理类的父类,如果被代理的类有一些属性或方法被final定义,是不一定能成功代理的
通过Proxy被手动重写的子类产生代理类,这个代理类的方法都会视为代理增强后的方法
调用这个代理类的方法其实是调用了MethodInterceptor的intercept(拦截)方法
增强(advice,也叫通知):对原方法额外进行的操作,一共有5种类型
连接点:可以被增强(进行AOP操作)的方法叫做连接点,几乎所有的方法都可以被称为连接点
切入点:实际上被增强的方法
切面:这不是一个名词,这是一个动词,使用AOP增强切入点的这样一个操作叫切面
目标:被通知的对象
Spring AOP现在已经集成了AspectJ,AspectJ算的上是Spring生态系统中最完整的AOP框架
AspectJ代理不同与Spring代理,Spring AOP属于运行时增强(基于java提供的类在运行时实现),而AspectJ是编译时增强(基于字节码操作在生成class文件时就进行改变),因此AspectJ在处理大量请求时性能上比SpringAOP好很多,因为在运行时不用读取二进制代理文件
同时现在大多企业都使用AspectJ(那学习SpringAOP底层有什么用吗?别问,我也不知道)
格式如下:
execution([权限修饰符] 返回值类型 包名.类名.方法名(参数列表))
说明:
eg:
execution(* com.example.demo.UserImpl.*(..))
@Aspect:表示这是一个增强类
@Before:前置通知,后面value跟着的是切入点表达式
@After:最终通知
@AfterReturning:后置通知
@AfterThrowing:异常通知
@Around:环绕通知,其中proceedingJoinPoint.proceed()语句代表执行被增强的方法,这就是为什么说它可以直接拿到目标对象以及要执行的方法
@Component:原件的意思,把该类实例化放入到spring容器中
@Component
@Aspect
public class ProxyUser {
@Before(value = "execution(* com.example.demo.UserImpl.name(..))")
public void before() {
System.out.println("before......");
}
@After(value = "execution(* com.example.demo.UserImpl.name(..))")
public void after() {
System.out.println("after......");
}
@AfterReturning(value = "execution(* com.example.demo.UserImpl.name(..))")
public void afterReturning() {
System.out.println("afterReturning......");
}
@AfterThrowing(value = "execution(* com.example.demo.UserImpl.name(..))")
public void afterThrowing() {
System.out.println("AfterThrowing......");
}
@Around(value = "execution(* com.example.demo.UserImpl.name(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("Around before......");
proceedingJoinPoint.proceed();
System.out.println("Around after......");
}
}
我们发现上面的5种增强里的路径都是一样的,在修改路径时太麻烦了,有没有可以将这些路径抽取出来的方法?
使用PointCut注解做公共切入点抽取
@Pointcut(value = "execution(* com.example.demo.UserImpl.name(..))")
private void pointCut() {}
@Before(value = "pointCut()")
public void before() {
System.out.println("before......");
}
每个增强类最多有5种增强,而一个切入点可能被很多个增强类增强,我们现在想控制多个切面的执行顺序,怎么办?
在增强类上使用Order注解,Order之中的数字越小,说明优先级越高,也就越先执行
@Order(1)
@Service
@Aspect
public class ProxyUser {...}