引言
AOP(面向切面编程)是一种思想,并不是Spring独有的,各种框架使用AOP的方式也不同,Spring的AOP是一种基于 方法拦截 的AOP。
一、动态代理
动态代理就是通过拦截器给一个方法进行修饰的过程。下面举例说明
1、JDK动态代理
例子如下:
Man.java 接口
public interface Man {
void sayHello();
}
Person类是Man的子接口
public class Person implements Man {
@Override
public void sayHello() {
System.out.println("hello");
}
}
Test
Person person = new Person();
person.sayHello();
这样无疑会输出hello。
现在这个person觉得自己说的话不够好,想找个 代理 帮他发言,而代理就是通过下面 这个拦截器产生的:
MyIntercepter.java
public class MyIntercepter implements InvocationHandler {
private Object proxied;
//提供一个方法,将受理人传入,并将代理对象返还回去
public Object getProxy(Object proxied){
this.proxied = proxied;
return Proxy.newProxyInstance(proxied.getClass().getClassLoader(),proxied.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("proxy add: before method invoke");//这句话是代理加的
Object obj = method.invoke(proxied,args);//这句话是person本人说的
System.out.println("proxy add: after method invoke");//这句话是代理加的
return obj;
}
}
MyIntercepter实现了InvocationHandler接口,他就是一个代理,通过重写invoke方法,给被代理对象的 方法 进行了一定加工。
由于代理要在受理人的方法基础上进行加工,那么代理的类型肯定不是Person,拦截器在底层生成了一个实现了Man接口的具体类,所以可以说他是Man类型,不能说他是Person类型, 测试如下:
public static void main(String[] args) {
MyIntercepter intercepter = new MyIntercepter();
Person person = new Person();
Man man = (Man) intercepter.getProxy(person);
man.sayHello();
}
打印的消息就加上了代理的额外信息。
2、CGLIB动态代理
CGLIB是第三方的动态代理方式,不属于JDK,要使用的话需要导入cglib的相关jar包。他的用法和JDK动态代理不同,他产生的代理的类型不是基于接口,而是基于实体类。
还是那个Person,这次他没有实现Man接口
public class Person {
public void sayHello() {
System.out.println("hello");
}
}
拦截器类
class MyIntercepter implements MethodInterceptor {
//提供一个方法传入受理人的类型,并返还代理对象
public Object getProxy(Class c){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(c);
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object proxy, Method method, Object[] methodArgs, MethodProxy methodProxy) throws Throwable {
System.out.println("proxy add: before method invoke");//代理加的
Object result = methodProxy.invokeSuper(proxy,methodArgs);//person本人说的
System.out.println("proxy add: aftermethod invoke");//代理加的
return result;
}
}
测试:
MyIntercepter intercepter = new MyIntercepter();
Person proxy = (Person) intercepter.getProxy(Person.class);
proxy.sayHello();
输出的结果就被代理包装过了。
这次,代理的类型是Person了,拦截器这次在底层创建了一个继承Person的实体类,并悄悄给Person的方法加上了修饰。
小结
如果你想对一个对象的方法进行一些前后的修饰,就可以用动态代理,在实际业务中,代理可能不只是会帮person多说两句话那么简单了,他可以帮person判断一些条件,如果条件不满足采取什么措施,或者抛异常了采取什么措施等等。如数据库事务中的出异常之后进行回滚事务。
二、Spring AOP概述
在一些和数据库结合的交易系统中,往往会比较复杂,数据库事务会和多个模块都有联系,比如交易记录和账户记录,然而在OOP看来,这两个模块是相互独立的,但是需要对这两个模块进行统一的事务管理,要么全部成功,要么全部失败
在交易过程中,交易记录和账户记录都是对象,这两个对象需要在在同一个事务中控制,这就是面向对象解决不了的问题了,这就要用 面向切面编程,这里的 切面环境就是数据库事务
Spring内部就是用了动态代理来实现AOP的,用户可以自定义一个拦截器,定义before、after等方法之后加上注解就能像之前的动态代理那样给方法增加额外的功能。
三、Spring AOP中的术语
切点 PointCut
切点可以告诉Spring AOP在什么时候启动拦截并织入对应的流程中,但不是所有时候都启动拦截,往往通过正则表达式限定。
通知 Advice
Advice指定在PointCut地方做什么事情(增强),打日志、执行缓存、处理异常等等。通知包括:
- 前置通知 before
- 后置通知 after
- 返回通知 afterReturning
- 异常通知 afterThrowing
- 环绕通知 around
连接点 join point
连接点是具体要拦截的东西,在Spring AOP中就是 某个对象的某个方法,Spring会通过切点的正则表达式去判断哪些方法是连接点
切面 Aspect
切面表示一个环境,可以将他理解为拦截器,因为他定义了通知、切点,并将他们织入到约定的流程中。
织入 weaving
织入就是生成代理对象,并将切面内容放到流程中的过程。
四、使用@AspectJ注解开发AOP
步骤如下:
1、选择连接点
Spring中的连接点就是方法,选择连接点就是决定拦截哪个方法,并向其中织入对应的AOP通知,我们继续用之前的例子,person想多说话,我们就将他说话的那个方法作为连接点。
person.java
@Component
public class Person implements Man {
public void sayHello() {
System.out.println("hello");
}
}
2、选择切面 Aspect
切面其实就是之前的拦截器,下面定义一个切面类
MyAspect.java
@Aspect
public class MyAspect {
@Before("execution(* Aspect.Person.sayHello())")
public void before(){
System.out.println("before...");
}
@After("execution(* Aspect.Person.sayHello())")
public void after(){
System.out.println("after...");
}
@AfterReturning("execution(* Aspect.Person.sayHello())")
public void afterReturning(){
System.out.println("afterReturning...");
}
@AfterThrowing("execution(* Aspect.Person.sayHello())")
public void afterThrowing(){
System.out.println("afterThrowing...");
}
}
Spring通过@before、@after等注解定义了通知,并规定通知织入哪个连接点(方法)。
execution(* Aspect.Person.sayHello())这句话就是切点,它通过正则表达式确定是拦截谁的方法。
3、装配Bean
AopConfig.java
@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class AopConfig {
@Bean
public MyAspect getAspect(){
return new MyAspect();
}
}
4、测试
Test.java
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class);
Man p = context.getBean(Man.class);
p.sayHello();
}
结果:
before...
hello
after...
afterReturning...
Spring提供了一个规则,当类的实现存在接口的时候,Spring采用JDK动态代理,当类不存在接口的时候,就采用CGLIB动态代理。