2、AOP

1、静态代理

image

1. 静态类⽂件数量过多, 不利于项⽬管理
   UserServiceImpl  -->  UserServiceProxy
   OrderServiceImpl -->  OrderServiceProxy
2. 额外功能维护性差
   要对每一个代理类修改功能(麻烦)

2、Spring 的动态代理开发

动态代理不需要定义类⽂件,都是在 JVM 运行过程中动态创建的,所以不会造成类文件数量过多,影响项目管理的问题

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.1.14.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.8</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.3</version>
</dependency>

2.1、UserServiceImpl

public class UserServiceImpl implements UserService {

    @Override
    public void login(String name, String password) {
        System.out.println("UserServiceImpl.login");
    }

    @Override
    public void register(User user) {
        System.out.println("UserServiceImpl.register");
    }
}

2.2、MethodBeforeAdvice

前置通知

public class Before implements MethodBeforeAdvice {

    /**
     * 作用: 需要在目标方法执行之前运行的额外功能, 书写在 before 方法中
     */
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("-----method before advice log------");
    }
}

2.3、MethodInterceptor

环绕通知

public class Arround implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Object ret = null;
        try {
            System.out.println("------前-----");
            ret = invocation.proceed();
            System.out.println("------后-----");
        } catch (RuntimeException e) {
            System.out.println("------异常-----");
        } finally {
            System.out.println("------最终-----");
        }
        return ret;
    }
}

2.4、配置

Spring 的工厂通过原始对象的 id 值获得的是代理对象

<!--目标对象-->
<bean id="userService" class="xxx.UserServiceImpl"/>

<!--增强对象-->
<bean id="before" class="xxx.Before"/>

<aop:config proxy-target-class="true">
    <aop:pointcut id="pc" expression="execution(* *(..))"/>
    <aop:advisor advice-ref="before" pointcut-ref="pc"/>
</aop:config>
<!--目标对象-->
<bean id="userService" class="xxx.UserServiceImpl"/>

<!--增强对象-->
<bean id="arround" class="xxx.Arround"/>

<aop:config proxy-target-class="true">
    <aop:pointcut id="pc" expression="execution(* *(..))"/>
    <aop:advisor advice-ref="arround" pointcut-ref="pc"/>
</aop:config>

3、切入点详解

3.1、切入点表达式

方法

* 第一个代表修饰符 + 返回值,第二个代表方法名
`* *(..)`                                  所有方法
`* login(..)`                              所有 login 方法
`* login(String,..)`                       .. 可以和具体的参数类型连用
`* login(String,String)`                   所有 login(String, String) 方法
`* register(com.zzw.proxy.User)`           非 java.lang 包中的类型, 必须要写全限定名

* 精准方法
* 第一个代表修饰符 + 返回值,第二个代表包 + 类 + 方法名
`* com.zzw.proxy.UserServiceImpl.login(..)`
`* com.zzw.proxy.UserServiceImpl.login(String,String)`

`* com.zzw.proxy.UserServiceImpl.*(..)`       类中的所有方法
`* *.UserServiceImpl.*(..)`                   一级包的 UserServiceImpl 类
`* *..UserServiceImpl.*(..)`                  多级包的 UserServiceImpl 类

`* com.zzw.proxy.*.*(..)`                不包括子包
`* com.zzw.proxy..*.*(..)`               包括子包

3.2、切入点函数

execution

最为重要的切入点函数,功能最全

* 最为重要的切入点函数,功能最全
* 执行:方法切入点表达式、类切入点表达式、包切入点表达式

* 弊端:execution 执行切入点表达式,书写麻烦
`execution(* com.zzw.proxy..*.*(..))`

* 注意:其他的切入点函数是 execution 书写复杂度的简化,功能上完全一致

args

* 作用: 主要用于函数(方法) 参数的匹配
* 切入点: 方法参数必须得是 2 个字符串类型的参数

`execution(* *(String,String))`
`args(String,String)`

within

* 作用: 主要用于进行类、包切入点表达式的匹配
* 切⼊点: UserServiceImpl 这个类

`execution(* *..UserServiceImpl.*(..))`
`within(*..UserServiceImpl)`

`execution(* com.baizhiedu.proxy..*.*(..))`
`within(com.baizhiedu.proxy..*)`

@annotation

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
}
* 作用: 为具有特殊注解的方法加入额外功能
`<aop:pointcut id="" expression="@annotation(com.zzw.Log)"/>`

3.3、切入点逻辑运算

and

* 案例: login, 参数 2个字符串
    `execution(* login(String,String))`
    `execution(* login(..)) and args(String,String)`
* 注意: 与操作不同用于同种类型的切入点函数

or

* 案例: register 方法 和 login 方法作为切入点
    `execution(* login(..)) or execution(* register(..))`

4、AOP 的底层实现原理

JDK 和 CGLib 动态代理、BeanPostProcessor

public class ProxyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("----- new Log-----");
                Object ret = method.invoke(bean, args);
                return ret;
            }
        };
        return Proxy.newProxyInstance(ProxyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), handler);
    }
}
<bean id="userService" class="com.zzw.factory.UserServiceImpl"/>

<!--
    1. 实现 BeanPostProcessor 进行加工
    2. 配置文件中对 BeanPostProcessor 进行配置   
-->
<bean id="proxyBeanPostProcessor" class="com.zzw.factory.ProxyBeanPostProcessor"/>

5、基于注解的 AOP 编程

5.1、四大通知

- 前置通知(before)             在切点运行 "之前" 执行
- 后置通知(after-returning)    在切点正常运行结束 "之后" 执行
- 异常通知(after-throwing)     在切点 "发生异常" 的时候执行
- 最终通知(after)              在切点的 "最终" 执行
try {
    前置通知(before)

    // 切点执行的位置

    后置通知(after-returning)
} catch (Exception e) {
    异常通知(after-throwing)
} finally {
    最终通知(after)
}
try {
    begin();             // 开启事务

    obj = pjp.proceed(); // 切点执行的位置

    commit();            // 提交事务
} catch (Throwable e) {
    rollback();          // 回滚事务
} finally {
    close();             // 关闭事务
}
return obj;

5.2、注解实现四大通知

@Aspect
@Component
public class Logger {

    // 声明切点
    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    private void pt() {
    }

    @Before("pt()")
    public void beforeMethod() {
        System.out.println("在方法执行之前运行");
    }

    @AfterReturning("pt()")
    public void afterReturnMethod() {
        System.out.println("在方法正常返回之后运行");
    }

    @AfterThrowing("pt()")
    public void afterThrowingMethod() {
        System.out.println("在方法执行出现异常时运行");
    }

    @After("pt()")
    public void afterMethod() {
        System.out.println("在方法最终运行");
    }
}
<!--注解扫描-->
<context:component-scan base-package="com.zzw"/>

<!--切面自动代理-->
<aop:aspectj-autoproxy/>

<!--告知 Spring 基于注解进行 AOP 编程, 默认 JDK 代理, true 为 CGLib 代理-->
<aop:aspectj-autoproxy proxy-target-class="true"/>

5.2、注解实现环绕通知

@Aspect
public class MyAspect {

    // 声明切点
    @Pointcut("execution(* com.zzw.service.impl.*.*(..))")
    private void pt() {
    }

    // 手动指定切点和增强的执行顺序
    // @Around("execution(* com.zzw.service.impl.*.*(..))")
    @Around("pt()")
    public Object aroundMethod(ProceedingJoinPoint pjp) {
        Object obj = null;
        try {
            System.out.println("在方法执行之前运行");

            obj = pjp.proceed(); // 切点执行的位置

            System.out.println(new Date().toLocaleString());    // 时间
            System.out.println(Arrays.toString(pjp.getArgs())); // 参数
            System.out.println(obj);                            // 返回值
            MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
            System.out.println(methodSignature.getMethod().getName()); // 方法名

            System.out.println("在方法正常返回之后运行");
        } catch (Throwable e) {                                 // 注意: 捕获的是 Throwable, 它是 Exception 的父接口
            System.out.println("在方法执行出现异常时运行");
        } finally {
            System.out.println("在方法最终运行");
        }
        return obj;
    }
}
<!--没有 @Component 就要配置 bean-->
<bean id="arround" class="xxx.MyAspect"/>

<!--切面自动代理-->
<aop:aspectj-autoproxy/>

<!--告知 Spring 基于注解进行 AOP 编程, 默认 JDK 代理, true 为 CGLib 代理-->
<aop:aspectj-autoproxy proxy-target-class="true"/>

5.3、0 xml 实现

@Configuration
@ComponentScan("com.zzw")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class SpringConfig {
}

6、AOP 的坑

* 坑: 在同⼀个业务类中, 进行业务方法间的相互调用, 只有最外层的方法, 才是加⼊了额外功能(内部的方法, 通过普通的方式调用, 都调用的是原始方法)
* 如果想让内层的方法也调用代理对象的方法, 就要实现 AppicationContextAware 获得工厂, 进而获得代理对象
public class UserServiceImpl implements UserService, ApplicationContextAware {

    private ApplicationContext ctx;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.ctx = applicationContext;
    }

    @Override
    public void register(User user) {
        System.out.println("UserServiceImpl.register");

        // 如果想让内层的方法也调用代理对象的方法, 就要实现 AppicationContextAware 获得工厂, 进而获得代理对象
        UserService userService = (UserService) ctx.getBean("userService");
        userService.login("suns", "123456");
    }

    @Override
    public boolean login(String name, String password) {
        System.out.println("UserServiceImpl.login");
        return true;
    }
}

7、AOP 阶段知识总结

- 前置通知(before)             在切点运行 "之前" 执行
- 后置通知(after-returning)    在切点正常运行结束 "之后" 执行
- 异常通知(after-throwing)     在切点 "发生异常" 的时候执行
- 最终通知(after)              在切点的 "最终" 执行
try {
    前置通知(before)

    // 切点执行的位置

    后置通知(after-returning)
} catch (Exception e) {
    异常通知(after-throwing)
} finally {
    最终通知(after)
}

image

posted @ 2023-07-12 12:18  lidongdongdong~  阅读(19)  评论(0)    收藏  举报