Spring的AOP理解
抽取方法的公共部分,而不是对象的公共部分
OOP面向对象,允许开发者定义纵向的关系,但并适用于定义横向的关系,导致了大量代码的重复,而不利于各个模块的重用。
(OOP, Object Oriented Programming,OOD(面向对象的设计),OOA(面向对象的分析))
AOP,一般称为面向切面,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理。
AOP实现的关键在于 代理模式,AOP代理主要分为静态代理和动态代理。
静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。
(1)AspectJ是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。
(2)Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:
①JDK动态代理
只提供接口的代理(被代理类需要实现接口,代理类实现同一个接口),不支持类的代理。
核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的实例, 生成目标类的代理对象。
②CGLIB动态代理:继承,生成子类
如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。
CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
JDK和CGlib动态代理区别
AOP的实现方式:动态代理的实现方式
JDK动态代理:利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
CGlib动态代理:利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
区别:
-
JDK动态代理只能对实现了接口的类生成代理,而不能针对类,使用的是 Java反射技术实现,生成类的过程比较高效。没有实现接口的类不能代理
-
CGlib是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法(继承),这种通过继承类的实现方式,不能代理final修饰的类。使用asm字节码框架实现,相关执行的过程比较高效,生成类的过程可以利用缓存弥补。
因为采用的是继承,所以该类或方法最好不要声明成final,对于final类或方法,是无法继承的。 -
JDK代理是不需要第三方库支持,只需要JDK环境就可以进行代理,使用条件: 实现InvocationHandler + 使用Proxy.newProxyInstance产生代理对象 + 被代理的对象必须要实现接口
-
CGLib必须依赖于CGLib的类库,但是它需要类来实现任何接口代理的,是指定的类生成一个子类,覆盖其中的方法,是一种继承
CGlib比JDK快?
使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在jdk6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。
在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理,总之,每一次jdk版本升级,jdk代理效率都得到提升,而CGLIB代理消息确有点跟不上步伐。
Spring在选择用JDK还是CGLiB的依据:
当Bean实现了接口时,Spring会用JDK的动态代理。
当Bean没有实现接口时,Spring使用CGlib来实现。
如果Bean实现了接口,要强制使用CGlib时,(添加CGLIB库,在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>
)。
何时使用JDK还是CGLiB?
针对接口编程的环境下推荐使用JDK的代理;
如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP。
如果目标对象实现了接口,可以强制使用CGLIB实现AOP。
如果目标对象没有实现接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换。
如何强制使用CGLIB实现AOP?
添加CGLIB库(aspectjrt-xxx.jar、aspectjweaver-xxx.jar、cglib-nodep-xxx.jar)
在Spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>
Java动态代理的两种实现方法
jdk动态代理是由java内部的反射机制来实现的,
cglib动态代理底层则是借助asm来实现的。
总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。
还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。
如果没有上述前提,jdk动态代理不能应用。
由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。
1、定义接口和实现
public interface UserService {
public String getName(int id);
public Integer getAge(int id);
}
public class UserServiceImpl implements UserService {
@Override
public String getName(int id) {
System.out.println("------getName------");
return "Tom";
}
@Override
public Integer getAge(int id) {
System.out.println("------getAge------");
return 10;
}
}
2、jdk动态代理实现
jdk动态代理是jdk原生就支持的一种代理方式,它的实现原理,就是通过让target类和代理类实现同一接口,代理类持有target对象,来达到方法拦截的作用。
这样通过接口的方式有两个弊端,一个是必须保证target类有接口,第二个是如果想要对target类的方法进行代理拦截,那么就要保证这些方法都要在接口中声明(目标子类自定义的方法无法代理),实现上略微有点限制。
Proxy类,静态方法newProxyInstance 三个参数
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces, InvocationHandler h)
loader 自然是类加载器
interfaces 代码要用来代理的接口
h 一个 InvocationHandler 对象
InvocationHandler接口:一个方法invoke(三个参数)
InvocationHandler 是一个接口,官方文档解释说,每个代理的实例都有一个与之关联的 InvocationHandler 实现类,如果代理的方法被调用,那么代理便会通知和转发给内部的 InvocationHandler 实现类,由它决定处理。
即:Proxy 动态产生的代理会调用 InvocationHandler 实现类,所以 InvocationHandler 是实际执行者。
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
proxy 代理对象 是最终生成的代理实例;
method 代理对象调用的方法 是被代理目标实例的某个具体方法;
args 调用的方法中的参数 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。
InvocationHandler 内部只是一个 invoke() 方法,正是这个方法决定了怎么样处理代理传递过来的方法调用。
1.定义InvocationHandler接口实现类 调用处理器
1.1 有参构造方法
构造方法传入目标对象,Object target 接收容器为Object类型,可以适用所有接口,复用同一增强
1.2 实现(重写)invoke方法
在invoke方法中执行目标方法method.invoke(target, args)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvocationHandler implements InvocationHandler {
private Object target;
MyInvocationHandler() {
super();
}
MyInvocationHandler(Object target) {
super();
this.target = target;
}
@Override
public Object invoke(Object o, Method method, Object[] args) throws Throwable {
if("getName".equals(method.getName())){
System.out.println("++++++before " + method.getName() + "++++++");
Object result = method.invoke(target, args);
System.out.println("++++++after " + method.getName() + "++++++");
return result;
} else {
Object result = method.invoke(target, args);
return result;
}
}
}
2.Proxy.newProxyInstance创建代理对象,执行目标方法
2.1 用实现类new一个目标接口类
2.2 new一个InvocationHandler 接口实现类
2.3 Proxy.newProxyInstance(目标接口的类加载器、目标接口的接口方法数组、InvocationHandler 接口实现类)
2.4 通过生成的代理类执行想要的方法 userServiceProxy.getName(1)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Main1 {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
InvocationHandler invocationHandler = new MyInvocationHandler(userService);
UserService userServiceProxy = (UserService)Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), invocationHandler);
System.out.println(userServiceProxy.getName(1));
System.out.println(userServiceProxy.getAge(1));
}
}
运行结果
++++++before getName++++++
------getName------
++++++after getName++++++
Tom
------getAge------
10
3、cglib动态代理实现
Cglib是一个优秀的动态代理框架,它的底层使用ASM在内存中动态的生成被代理类的子类,使用CGLIB即使代理类没有实现任何接口也可以实现动态代理功能。CGLIB具有简单易用,它的运行速度要远远快于JDK的Proxy动态代理:
cglib有两种可选方式,继承和引用。
第一种是基于继承实现的动态代理,所以可以直接通过super调用target方法,但是这种方式在spring中是不支持的,因为这样的话,这个target对象就不能被spring所管理,所以cglib还是采用类似jdk的方式(第2种:引用),通过持有target对象来达到拦截方法的效果。
CGLIB的核心类:
net.sf.cglib.proxy.Enhancer – 主要的增强类
net.sf.cglib.proxy.MethodInterceptor接口 – 主要的方法拦截类,它是Callback接口的子接口,需要用户实现
net.sf.cglib.proxy.MethodProxy – JDK的java.lang.reflect.Method类的代理类,可以方便的实现对源对象方法的调用,如使用:
Object o = methodProxy.invokeSuper(proxy, args); //虽然第一个参数是被代理对象,也不会出现死循环的问题。
1.定义net.sf.cglib.proxy.MethodInterceptor接口实现类 方法拦截器
1.1 不需要通过构造方法传入目标对象
1.2 实现intercept方法,4个参数 比jdk动态代理多了一个MethodProxy
1.3 通过methodProxy.invokeSuper(o, args)执行目标方法要比java.lang.reflect.Method反射对象执行要快
是最通用的回调(callback)类型,它经常被基于代理的AOP用来实现拦截(intercept)方法的调用。这个接口只定义了一个方法
public Object intercept(Object object, java.lang.reflect.Method method,
Object[] args, MethodProxy proxy) throws Throwable;
object 代理对像
method 拦截的方法
args 方法的参数
proxy
原来的方法可能通过使用java.lang.reflect.Method对象的一般反射调用,或者使用 net.sf.cglib.proxy.MethodProxy对象调用。net.sf.cglib.proxy.MethodProxy通常被首选使用,因为它更快。
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxy implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("++++++before " + methodProxy.getSuperName() + "++++++");
System.out.println(method.getName());
Object o1 = methodProxy.invokeSuper(o, args);
System.out.println("++++++before " + methodProxy.getSuperName() + "++++++");
return o1;
}
}
2.通过net.sf.cglib.proxy.Enhancer类获取代理类
2.1 new一个MethodInterceptor 接口实现类
2.2 new一个Enhancer对象
2.3 设置Enhancer对象的父类setSuperclass 回调函数setCallback
2.4 enhancer.create() 创建代理类
2.5 代理类调用方法 o.getName(1)
package com.meituan.hyt.test3.cglib;
import com.meituan.hyt.test3.service.UserService;
import com.meituan.hyt.test3.service.impl.UserServiceImpl;
import net.sf.cglib.proxy.Enhancer;
public class Main2 {
public static void main(String[] args) {
CglibProxy cglibProxy = new CglibProxy();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserServiceImpl.class);
enhancer.setCallback(cglibProxy);
UserService o = (UserService)enhancer.create();
o.getName(1);
o.getAge(1);
}
}
4、需要注意的问题
需要注意的是,当一个方法没有被aop事务包裹,在该方法内部去调用另外一个有aop事务包裹的方法时(同一个类中),这个方法的aop事务不会生效。比如:
public void register() {
aopRegister();
}
@Transactional
public void aopRegister() {
}
}
原因:
在spring中,无论通过jdk的形式还是cglib的形式,代理类对target对象的方法进行拦截,其实都是通过让代理类持有target对象的引用,当外部引用aop包围的方法时,调用的其实是代理类对应的方法,代理类持有target对象,便可以控制target方法执行时的全方位拦截。
而如果在target的内部方法register调用一个aop包围的target方法aopRegister,调用的其实就是target自身的方法,因为这时候的this指针是不可能指向代理类的。所以事务是不能生效的。
Spring事务失效的几种原因
原因是在同一个类之中,方法互相调用,切面无效 ,而不仅仅是事务。这里事务之所以无效,是因为spring的事务是通过aop实现的。
java静态代理
首先得有一个接口,通用的接口是代理模式实现的基础
java动态代理实现与原理详细分析
public interface Movie {
void play();
}
// 一个真正的实现这个 Movie 接口的类
public class RealMovie implements Movie {
@Override
public void play() {
System.out.println("您正在观看电影 《盗梦空间》");
}
}
// Proxy代理 一个只是实现接口的代理类
public class Cinema implements Movie {
RealMovie movie;
public Cinema(RealMovie movie) {
super();
this.movie = movie;
}
@Override
public void play() {
guanggao(true);
movie.play();
guanggao(false);
}
public void guanggao(boolean isStart) {
if (isStart) {
System.out.println("电影马上开始了,爆米花、可乐、口香糖9.8折,快来买啊!");
} else {
System.out.println("电影马上结束了,爆米花、可乐、口香糖8.8折,买回家吃吧!");
}
}
}
// Cinema 就是 Proxy 代理对象,它有一个 play() 方法。不过调用 play() 方法时,它进行了一些相关利益的处理,那就是广告。现在,我们编写测试代码。
public class ProxyTest {
public static void main(String[] args) {
RealMovie realMovie = new RealMovie();
Movie movie = new Cinema(realMovie);
movie.play();
}
}
现在可以看到,代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。
值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。
上面介绍的是静态代理的内容,为什么叫做静态呢?因为它的类型是事先预定好的,比如上面代码中的 Cinema 这个类。
动态代理的秘密
为什么 Proxy 能够动态产生不同接口类型的代理?
通过传入进去的接口,然后通过反射动态生成了一个接口实例。
比如 SellWine 是一个接口,那么 Proxy.newProxyInstance() 内部肯定会有 ==new SellWine(); == 这样相同作用的代码,不过它是通过反射机制创建的。
源码是 1.8 版本:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces, InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
newProxyInstance 的确创建了一个实例,它是通过 cl 这个 Class 文件的构造方法反射生成。cl 由 getProxyClass0() 方法获取。
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
return proxyClassCache.get(loader, interfaces);
}
直接通过缓存获取,如果获取不到,注释说会通过 ProxyClassFactory 生成。
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
// Proxy class 的前缀是 “$Proxy”,
private static final String proxyClassNamePrefix = "$Proxy";
// next number to use for generation of unique proxy class names
private static final AtomicLong nextUniqueNumber = new AtomicLong();
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
for (Class<?> intf : interfaces) {
}
}
}
这个类的注释说,通过指定的 ClassLoader 和 接口数组 用工厂方法生成 proxy class。 然后这个 proxy class 的名字是:
// Proxy class 的前缀是 “$Proxy”,
private static final String proxyClassNamePrefix = "$Proxy";
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
所以,动态生成的代理类名称是包名+$Proxy+id序号。
生成的过程,核心代码如下:
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
这两个方法,我没有继续追踪下去,defineClass0() 甚至是一个 native 方法。我们只要知道,动态创建代理这回事就好了。
验证一下动态生成的代理类的名字是不是包名+$Proxy+id序号:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class SellWineTest {
public static void main(String[] args) {
MaotaiJiu maotaiJiu = new MaotaiJiu();
Wuliangye wu = new Wuliangye();
InvocationHandler riemann = new GuitaiA(maotaiJiu);
InvocationHandler riemann2 = new GuitaiA(wu);
SellWine dynamicProxy = (SellWine) Proxy.newProxyInstance(MaotaiJiu.class.getClassLoader(),
MaotaiJiu.class.getInterfaces(), riemann);
SellWine dynamicProxy2 = (SellWine) Proxy.newProxyInstance(Wuliangye.class.getClassLoader(),
Wuliangye.class.getInterfaces(),riemann2);
dynamicProxy.mainJiu();
dynamicProxy2.mainJiu();
System.out.println("dynamicProxy class name:" + dynamicProxy.getClass().getName());
System.out.println("dynamicProxy1 class name:" + dynamicProxy2.getClass().getName());
}
}
输出结果:
dynamicProxy class name:com.sun.proxy.$Proxy0
dynamicProxy2 class name:com.sun.proxy.$Proxy0
接口的代理类名是:com.sun.proxy.Proxy0
这说明动态生成的 proxy class 与 Proxy 这个类同一个包。
红框中 $Proxy0就是通过 Proxy 动态生成的。
$Proxy0实现了要代理的接口。
$Proxy0通过调用 InvocationHandler来执行任务。
总结
- 代理分为静态代理和动态代理两种。
- 静态代理,代理类需要自己编写代码写成。
- jdk动态代理,代理类通过 Proxy.newInstance() 方法生成。
- 不管是静态代理还是动态代理,代理与被代理者都要实现两样接口,它们的实质是面向接口编程。
- 静态代理和动态代理的区别是在于要不要开发者自己定义 Proxy 类。
- 动态代理通过 Proxy 动态生成 proxy class,但是它也指定了一个 InvocationHandler 的实现类。
- 代理模式本质上的目的是为了增强现有代码的功能。
代理
(1)、什么是代理?
大道理上讲代理是一种软件设计模式,目的地希望能做到代码重用。具体上讲,代理这种设计模式是通过不直接访问被代理对象的方式,而访问被代理对象的方法。这个就好比 商户---->明星经纪人(代理)---->明星这种模式。我们可以不通过直接与明星对话的情况下,而通过明星经纪人(代理)与其产生间接对话。
(2)、什么情况下使用代理?
a.设计模式中有一个设计原则是开闭原则,是说对修改关闭、对扩展开放,我们在工作中有时会接手很多前人的代码,里面代码逻辑让人摸不着头脑(sometimes the code is really like shit),这时就很难去下手修改代码,那么这时我们就可以通过代理对类进行增强。
b.我们在使用RPC框架的时候,框架本身并不能提前知道各个业务方要调用哪些接口的哪些方法。那么这个时候,就可用通过动态代理的方式来建立一个中间人给客户端使用,也方便框架进行搭建逻辑,某种程度上也是客户端代码和框架松耦合的一种表现。
c.Spring的AOP机制就是采用动态代理的机制来实现切面编程。
静态代理与动态代理区别
我们根据加载被代理类的时机不同,将代理分为静态代理和动态代理。
如果我们在代码编译时就确定了被代理的类是哪一个,那么就可以直接使用静态代理;
如果不能确定,那么可以使用类的动态加载机制,在代码运行期间加载被代理的类这就是动态代理,比如RPC框架和Spring AOP机制。
AOP静态代理与动态代理区别:
在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。
既然是代理,那么动态代理与静态代理的功能与目的是没有区别的,唯一有区别的就是动态与静态的差别。
区别:手动创建、自动创建
那么在动态代理的中这个动态体现在什么地方?
静态代理,需要手动编写代码实现目标接口,而在动态代理中,可以让程序在运行的时候自动在内存中创建一个实现目标接口的代理类,而不需要手动创建。这就是它被称为动态的原因。
解释一下Spring AOP里面的几个名词(术语):
(1)切面(Aspect):被抽取的公共模块,可能会横切多个对象。 在Spring AOP中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @AspectJ 注解来实现。
- 切面,是动作,把通知应用到切入点过程
(2)连接点(Join point):指方法,在Spring AOP中,一个连接点 总是 代表一个方法的执行。
- 类里面那些方法可以被增强,这些方法称为连接点
(3)通知(Advice):在切面的某个特定的连接点(Join point)上执行的动作。通知有各种类型,其中包括“around”、“before”和“after”等通知。许多AOP框架,包括Spring,都是以拦截器做通知模型, 并维护一个以连接点为中心的拦截器链。
- 实际增强的逻辑部分称为通知(增强)
(4)切入点(Pointcut):切入点是指 我们要对哪些Join point进行拦截的定义。通过切入点表达式,指定拦截的方法,比如指定拦截add*、search*。
- 实际被真正增强的方法,称为切入点
(5)引入(Introduction):(也被称为内部类型声明(inter-type declaration))。声明额外的方法或者某个类型的字段。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,你可以使用一个引入来使bean实现 IsModified 接口,以便简化缓存机制。
(6)目标对象(Target Object): 被一个或者多个切面(aspect)所通知(advise)的对象。也有人把它叫做 被通知(adviced) 对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理(proxied) 对象。
(7)织入(Weaving):指把增强应用到目标对象来创建新的代理对象的过程。Spring是在运行时完成织入。
切入点(pointcut)和连接点(join point)匹配的概念是AOP的关键,这使得AOP不同于其它仅仅提供拦截功能的旧技术。 切入点使得定位通知(advice)可独立于OO层次。 例如,一个提供声明式事务管理的around通知可以被应用到一组横跨多个对象中的方法上(例如服务层的所有业务操作)。
Spring AOP通知有哪些类型?5种
(1)前置通知(Before advice):在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。
(2)后置通知:返回后通知(After returning advice):在某连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
(3)异常通知:抛出异常后通知(After throwing advice):在方法抛出异常退出时执行的通知。
(4)最终通知:后通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
(5)环绕通知(Around Advice):包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。 环绕通知是最常用的一种通知类型。大部分基于拦截的AOP框架,例如Nanning和JBoss4,都只提供环绕通知。
同一个aspect,不同advice的执行顺序:
①没有异常情况下的执行顺序:(afterThrowing不执行,其他通知都会执行)
around before advice
before advice
target method 执行
around after advice
after advice
afterReturning
②有异常情况下的执行顺序:(afterReturning不执行)
around before advice
before advice
target method 执行
around after advice
after advice
afterThrowing:异常发生
java.lang.RuntimeException: 异常发生
AOP 操作(准备工作)
1、Spring 框架一般都是基于 AspectJ 实现 AOP 操作
(1)AspectJ 不是 Spring 组成部分,独立 AOP 框架,一般把 AspectJ 和 Spirng 框架一起使 用,进行 AOP 操作
2、基于 AspectJ 实现 AOP 操作
(1)基于 xml 配置文件实现
(2)基于注解方式实现(使用)
3、在项目工程里面引入 AOP 相关依赖
4、切入点表达式
(1)切入点表达式作用:知道对哪个类里面的哪个方法进行增强 (2)语法结构: execution([权限修饰符] [返回类型] [类全路径] [方法名称]([参数列表]) )
- 关键字 execution(表达式)
pointcut="execution( *..*.*(..))
-
表达式
访问修饰符 返回值 包名.包名.包名…类名.方法名(参数列表) -
标准的表达式写法
public void cn.luis.service.impl.AccountServiceImpl.saveAccount()
- 全通配写法
*..*.*(..)
- 变化过程
- 访问修饰符可省略
void cn.luis.service.impl.AccountServiceImpl.saveAccount()
- 返回值可使用通配符*
* cn.luis.service.impl.AccountServiceImpl.saveAccount()
举例 1:对 com.atguigu.dao.BookDao 类里面的 add 进行增强
execution(* com.atguigu.dao.BookDao.add(..))
举例 2:对 com.atguigu.dao.BookDao 类里面的所有的方法进行增强
execution(* com.atguigu.dao.BookDao.* (..))
举例 3:对 com.atguigu.dao 包里面所有类,类里面所有方法进行增强
execution(* com.atguigu.dao.*.* (..))
AOP 操作(AspectJ 注解)
1、创建类,在类里面定义方法
public class User {
public void add() {
System. out .println("add.......");
}
}
2、创建增强类(编写增强逻辑)
(1)在增强类里面,创建方法,让不同方法代表不同通知类型
// 增强的类
public class UserProxy {
public void before() {
// 前置通知
System. out .println("before......");
}
}
3、进行通知的配置
(1)在 spring配置文件中,开启注解扫描
<? xml version="1.0" encoding="UTF-8" ?>
<beans >
<! -- 开启注解扫描 -- >
<context:component-scan basepackage="com.atguigu.spring5.aopanno"></context:component-scan>
(2)使用注解创建 User 和 UserProxy 对象
(3)在增强类上面添加注解@Aspect
// 增强的类
@Component
@Aspect
// 生成代理对象
public class UserProxy
{
}
(4)在 spring 配置文件中开启生成代理对象
<! -- 开启 Aspect 生成代理对象 -- >
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
4、配置不同类型的通知
(1)在增强类的里面,在作为通知方法上面添加通知类型注解,使用切入点表达式配置
// 增强的类
@Component
@Aspect
// 生成代理对象
public class UserProxy {
// 前置通知
//@Before 注解表示作为前置通知
@Before(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void before() {
System. out .println("before.........");
}
// 后置通知(返回通知)
@AfterReturning(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void afterReturning() {
System. out .println("afterReturning.........");
}
// 最终通知
@After(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void after() {
System. out .println("after.........");
}
// 异常通知
@AfterThrowing(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void afterThrowing() {
System. out .println("afterThrowing.........");
}
// 环绕通知
@Around(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System. out .println("环绕之前.........");
// 被增强的方法执行
proceedingJoinPoint.proceed();
System. out .println("环绕之后.........");
}
}
5、相同的切入点抽取
// 相同切入点抽取
@Pointcut(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void pointdemo()
{
}
// 前置通知 //@Before 注解表示作为前置通知
@Before(value = "pointdemo()")
public void before() {
System. out .println("before.........");
}
6、有多个增强类对同一个方法进行增强,设置增强类优先级
(1)在增强类上面添加注解
@Order(数字类型值),数字类型值越小优先级越高
@Component
@Aspect
@Order(1)
// @Order(Ordered.HIGHEST_PRECEDENCE) // -2147483648
public class PersonProxy
public interface Ordered {
int HIGHEST_PRECEDENCE = -2147483648;
int LOWEST_PRECEDENCE = 2147483647;
int getOrder();
}
7、完全使用注解开发
(1)创建配置类==@EnableAspectJAutoProxy==,不需要创建 xml 配置文件
@Configuration
@ComponentScan(basePackages = {"com.atguigu"}) @EnableAspectJAutoProxy(proxyTargetClass = true)
public class ConfigAop {
}
AOP 操作(AspectJ 配置文件)
1、创建两个类,增强类和被增强类,创建方法
2、在 spring 配置文件中创建两个类对象
<! -- 创建对象 -- >
<bean id="book" class="com.atguigu.spring5.aopxml.Book"></bean>
<bean id="bookProxy" class="com.atguigu.spring5.aopxml.BookProxy"></bean>
3、在 spring 配置文件中配置切入点
<! -- 配置 aop 增强 -- >
<aop:config>
<! -- 切入点 -- >
<aop:pointcut id="p" expression="execution(* com.atguigu.spring5.aopxml.Book.buy(..))"/>
<! -- 配置切面 -- >
<aop:aspect ref="bookProxy">
<! -- 增强作用在具体的方法上 -- >
<aop:before method="before" pointcut-ref="p"/> </aop:aspect> </aop:config>