SpringAop的本质就是动态代理
在Spring源码中,用到的动态代理主要有两种,JDK动态代理以及CGLib动态代理。两者主要区别是:
- 基于接口代理(JDK代理)
基于接口代理,凡是类的方法非public修饰,或者用了static关键字修饰,那这些方法都不能被Spring AOP增强
- 基于CGLib代理(子类代理)
目标对象没有实现接口,则默认会采用CGLIB代理。如果目标对象实现了接口,可以强制使用CGLIB实现代理(添加CGLIB库)
基于子类代理,凡是类的方法使用了private、static、final修饰,那这些方法都不能被Spring AOP增强
JDK代理
静态代理模式代码:
public interface PersonService {
void addPerson();
void delete();
}
class PersonServiceImpl implements PersonService{
@Override
public void addPerson() {
System.out.println("生成一个人");
}
@Override
public void delete() {
System.out.println("删除一个人");
}
}
class PesonServiceImplProxy implements PersonService{
// 注入目标对象 实际就是真正实现接口的实现类
private PersonServiceImpl personService;
public PesonServiceImplProxy(PersonServiceImpl personService){
this.personService = personService;
}
/**
* 代理对象不会改变原有目标对象的方法 只会在生成的代理对象增强原来已有的方法的表现形式
* @author zhangjun
* @datetime 2019/6/6 16:03
* @param
* @return
*/
@Override
public void addPerson() {
// before 增强方法
System.out.println("before");
// 还是要调用目标方法来执行原有逻辑
personService.addPerson();
// after增强方法
System.out.println("after");
}
@Override
public void delete() {
}
}
接口实现动态代理代码:
public interface PersonService {
void addPerson();
void delete();
}
class PersonServiceImpl implements PersonService{
@Override
public void addPerson() {
System.out.println("生成一个人");
}
@Override
public void delete() {
System.out.println("删除一个人");
}
}
class JdkDynmicProxy implements InvocationHandler{
private PersonServiceImpl personService;
public JdkDynmicProxy(PersonServiceImpl personService){
this.personService = personService;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before");
Object invoke = method.invoke(personService, args);
System.out.println("after");
return invoke;
}
}
class Client {
public static void main(String[] args) throws Exception{
PersonService o = (PersonService)Proxy.newProxyInstance(PersonService.class.getClassLoader(),
new Class[]{PersonService.class},
new JdkDynmicProxy(new PersonServiceImpl()));
o.addPerson();
}
}
<font color="red">动态代理比静态代理的好处是,当接口添加方法动态代理不需要改变代码,而静态代理需要手动添加新增的代码</font>
CGLib代理模式实现
代码示例
public interface PersonService {
void addPerson();
void delete();
}
class PersonServiceImpl implements PersonService{
@Override
public void addPerson() {
System.out.println("生成一个人");
}
@Override
public void delete() {
System.out.println("删除一个人");
}
}
class CglibProxy implements MethodInterceptor{
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("增强的方法");
Object invoke = method.invoke(o, objects);
return invoke;
}
}
class ClientProxy{
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(PersonServiceImpl.class);
enhancer.setCallback(new CglibProxy());
PersonService o = (PersonService)enhancer.create();
o.addPerson();
}
}
相同点:
两种动态代理本质上都是:字节码组装,主要还是通过反射的逻辑
AOP动态代理的应用场景:
- 日志
- 事务
- 权限
- 缓存
- 懒加载
可以简单认为如果一个类上使用了aspect就是切面,其中aspect由pointcut和advice组成。
advice介绍
由 aspect 添加到特定的 join point(即满足 point cut 规则的 join point) 的一段代码.
许多 AOP框架, 包括 Spring AOP, 会将 advice 模拟为一个拦截器(interceptor), 并且在 join point 上维护多个 advice, 进行层层拦截.
例如 HTTP 鉴权的实现, 我们可以为每个使用 RequestMapping 标注的方法织入 advice, 当 HTTP 请求到来时, 首先进入到 advice 代码中, 在这里我们可以分析这个 HTTP 请求是否有相应的权限, 如果有, 则执行 Controller, 如果没有, 则抛出异常. 这里的 advice 就扮演着鉴权拦截器的角色了.
advice的类型
- before advice, 在 join point 前被执行的 advice. 虽然 before advice 是在 join point 前被执行, 但是它并不能够阻止 join point 的执行, 除非发生了异常(即我们在 before advice 代码中, 不能人为地决定是否继续执行 join point 中的代码)
- after return advice, 在一个 join point 正常返回后执行的 advice
- after throwing advice, 当一个 join point 抛出异常后执行的 advice
- after(final) advice, 无论一个 join point 是正常退出还是发生了异常, 都会被执行的 advice.
- around advice, 在 join point 前和 joint point 退出后都执行的 advice. 这个是最常用的 advice.
连接点(join point)
程序运行中的一些时间点, 例如一个方法的执行, 或者是一个异常的处理.在 Spring AOP 中, join point 总是方法的执行点, 即只有方法连接点.
切点(point cut)
<font color = "red">在 Spring 中, 所有的方法都可以认为是 joinpoint, 但是我们并不希望在所有的方法上都添加 Advice, 而 pointcut 的作用就是提供一组规则来匹配joinpoint, 给满足规则的 joinpoint 添加 Advice.</font>
introduction
为一个类型添加额外的方法或字段. Spring AOP 允许我们为 目标对象 引入新的接口(和对应的实现)
<font color="red">目标对象(Target)</font>
织入 advice 的目标对象. 目标对象也被称为 advised object.因为 Spring AOP 使用运行时代理的方式来实现 aspect, 因此 adviced object 总是一个代理对象(proxied object)</br>
<font color = "red">注意:adviced object 指的不是原来的类, 而是织入 advice 后所产生的代理类.</font>
声明pointcut
一个 pointcut 的声明由两部分组成:
一个方法签名, 包括方法名和相关参数
一个 pointcut 表达式, 用来指定哪些方法执行是我们感兴趣的(即因此可以织入 advice).
在@AspectJ 风格的 AOP 中, 我们使用一个方法来描述 pointcut, 即:
@Pointcut("execution(* com.xys.service.UserService.*(..))") // 切点表达式
private void dataAccessOperation() {} // 切点前面
<font color= "red">上面我们简单地定义了一个 pointcut, 这个 pointcut 所描述的是: 匹配所有在包 com.xys.service.UserService 下的所有方法的执行.</font>
within
匹配特定包下的所有 join point, 例如 within(com.xys.) 表示 com.xys 包中的所有连接点, 即包中的所有类的所有方法. 而 within(com.xys.service.Service) 表示在 com.xys.service 包中所有以 Service 结尾的类的所有的连接点.
this 与 target(<font color = "red">不理解需要重新梳理</font>)
this 的作用是匹配一个 bean, 这个 bean(Spring AOP proxy) 是一个给定类型的实例(instance of). 而 target 匹配的是一个目标对象(target object, 即需要织入 advice 的原始的类), 此对象是一个给定类型的实例(instance of)。
常见的切点表达式
匹配 bean 名字为指定值的 bean 下的所有方法, 例如:
bean(*Service) // 匹配名字后缀为 Service 的 bean 下的所有方法
bean(myService) // 匹配名字为 myService 的 bean 下的所有方法
args
匹配参数满足要求的的方法.
例如:
@Pointcut("within(com.xys.demo2.*)")
public void pointcut2() {
}
@Before(value = "pointcut2() && args(name)")
public void doSomething(String name) {
logger.info("---page: {}---", name);
}
@Service
public class NormalService {
private Logger logger = LoggerFactory.getLogger(getClass());
public void someMethod() {
logger.info("---NormalService: someMethod invoked---");
}
public String test(String name) {
logger.info("---NormalService: test invoked---");
return "服务一切正常";
}
}
当 NormalService.test 执行时, 则 advice doSomething 就会执行, test 方法的参数 name 就会传递到 doSomething 中.
常用例子
// 匹配只有一个参数 name 的方法
@Before(value = "aspectMethod() && args(name)")
public void doSomething(String name) {
}
// 匹配第一个参数为 name 的方法
@Before(value = "aspectMethod() && args(name, ..)")
public void doSomething(String name) {
}
// 匹配第二个参数为 name 的方法
Before(value = "aspectMethod() && args(*, name, ..)")
public void doSomething(String name) {
}
@annotation
匹配由指定注解所标注的方法, 例如:
@Pointcut("@annotation(com.xys.demo1.AuthChecker)")
public void pointcut() {
}
匹配方法签名
// 匹配指定包中的所有的方法
execution(* com.xys.service.*(..))
// 匹配当前包中的指定类的所有方法
execution(* UserService.*(..))
// 匹配指定包中的所有 public 方法
execution(public * com.xys.service.*(..))
// 匹配指定包中的所有 public 方法, 并且返回值是 int 类型的方法
execution(public int com.xys.service.*(..))
// 匹配指定包中的所有 public 方法, 并且第一个参数是 String, 返回值是 int 类型的方法
execution(public int com.xys.service.*(String name, ..))
匹配类型签名
// 匹配指定包中的所有的方法, 但不包括子包
within(com.xys.service.*)
// 匹配指定包中的所有的方法, 包括子包
within(com.xys.service..*)
// 匹配当前包中的指定类中的方法
within(UserService)
// 匹配一个接口的所有实现类中的实现的方法
within(UserDao+)
匹配 Bean 名字
// 匹配以指定名字结尾的 Bean 中的所有方法
bean(*Service)
切点表达式组合
// 匹配以 Service 或 ServiceImpl 结尾的 bean
bean(*Service || *ServiceImpl)
// 匹配名字以 Service 结尾, 并且在包 com.xys.service 中的 bean
bean(*Service) && within(com.xys.service.*)