AOP

实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。

在运行期间通过代理方式向目标类植入增强的代码 有两种实现方式:基于接口的 JDK 动态代理和基于继承的 CGLIB 动态代理。

AOP使用场景

a. 作⽤:AOP技术,利利⽤用⼀一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了了多个类的公共⾏行行为封装到⼀个可重⽤用模块,并将其名为“Aspect”,即⽅方⾯面。所谓“⽅方⾯面”,简单地说,就是将那些与业务⽆无关,却为业务模块所共同调⽤用的逻辑或责任封装起来,便便于减少系统的重复代码,降低模块间的耦合度,并有利利于未来的可操作性和可维护性。
b. 实现:
i. ⼀一是采⽤用动态代理理技术,利利⽤用截取消息的⽅方式,对该消息进⾏行行装饰,以取代原有对象⾏行行为的执⾏行行;
ii. ⼆二是采⽤用静态织⼊入的⽅方式,引⼊入特定的语法创建“⽅方⾯面”,从⽽而使得编译器器可以在编译期间织⼊入有关“⽅方⾯面”的代
码。
c.AOP用来封装横切关注点,具体可以在下面的场景中使用:

  1. Authentication 权限

  2. Caching 缓存

  3. Context passing 内容传递

  4. Error handling 错误处理

  5. Lazy loading 懒加载

  6. Debugging  调试

  7. logging, tracing, profiling and monitoring 记录跟踪 优化 校准

  8. Performance optimization 性能优化

  9. Persistence  持久化

  10. Resource pooling 资源池

  11. Synchronization 同步

  12. Transactions 事务

最常见的一些横切行为如下面这些:

  • 日志记录,跟踪,优化和监控
  • 事务的处理
  • 持久化
  • 性能的优化
  • 资源池,如数据库连接池的管理
  • 系统统一的认证、权限管理等
  • 应用系统的异常捕捉及处理

d.AOP相关概念
这里只是重温 AspectJ 中几个必须要了解的概念:
Aspect: Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。

Joint point:表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。

Pointcut:表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。

Advice:Advice 定义了在 pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。

  • 切面(Aspect):一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。方面用Spring的 Advisor或拦截器实现。

  • 连接点(Joinpoint): 程序执行过程中明确的点,如方法的调用或特定的异常被抛出。

  • 通知(Advice): 在特定的连接点,AOP框架执行的动作。各种类型的通知包括“around”、“before”和“throws”通知。通知类型将在下面讨论。许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。Spring中定义了四个advice: BeforeAdvice, AfterAdvice, ThrowAdvice和DynamicIntroductionAdvice

  • 切入点(Pointcut): 指定一个通知将被引发的一系列连接点的集合。AOP框架必须允许开发者指定切入点:例如,使用正则表达式。 Spring定义了Pointcut接口,用来组合MethodMatcher和ClassFilter,可以通过名字很清楚的理解, MethodMatcher是用来检查目标类的方法是否可以被应用此通知,而ClassFilter是用来检查Pointcut是否应该应用到目标类上

  • 引入(Introduction): 添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现 IsModified接口,来简化缓存。Spring中要使用Introduction, 可有通过DelegatingIntroductionInterceptor来实现通知,通过DefaultIntroductionAdvisor来配置Advice和代理类要实现的接口

  • 目标对象(Target Object): 包含连接点的对象。也被称作被通知或被代理对象。POJO

  • AOP代理(AOP Proxy): AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。

  • 织入(Weaving): 组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。

image

何时使用JDK还是CGLiB?

1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP。

2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP。

3、如果目标对象没有实现了接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换。

1.JDK动态代理

Spring JDK 动态代理需要实现 InvocationHandler 接口,重写 invoke 方法,客户端使用 Java.lang.reflect.Proxy 类产生动态代理类的对象。

代码示例:

public class Book {

    private int id;
    private String name;
    private String author;

    public Book(int id, String name, String author) {
        this.id = id;
        this.name = name;
        this.author = author;
    }

    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", author='" + author + '\'' +
                '}';
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

}
定义两个接口:
package com.yxkj.spring_aop_demo.service;

import com.yxkj.spring_aop_demo.entity.Book;

import java.util.List;

public interface BookService {

    Book selectById(int id);
    List<Book> selectAll();
    void addBook(Book book);
    void updateBook(Book book);
}

public interface BookChildService {

     Book selectCilidByid(int id);
}
实现类:
public class BookServiceImpl implements BookService,BookChildService {
    @Override
    public Book selectById(int id) {
        System.out.println("selectById正在执行根据ID查询Book数据@");
        return null;
    }

    @Override
    public List<Book> selectAll() {
        System.out.println("selectAll正在执行查询所有数据!");
        return null;
    }

    @Override
    public void addBook(Book book) {
        System.out.println("addBook执行添加Book操作!");
        System.out.println("name="+book.getName()+";author="+book.getAuthor());
    }

    @Override
    public void updateBook(Book book) {
        System.out.println("updateBook执行修改操作!");
        System.out.println("name="+book.getName()+";author="+book.getAuthor());
    }

    @Override
    public Book selectCilidByid(int id) {
        System.out.println("selectCilidByid查询数据");
        return null;
    }
}

Aspect切面类,定义增强方法:
/**
 * @USER: 
 * @DATE: 2021-07-14
 * @description: MyAspect 类在切面中定义了两个增强的方法,分别为 myBefore 和 myAfter。
 */
public class MyAspect {

    public void myBefore(){
        System.out.println("---方法执行之前-----");
    }
    public void myAfter(){
        System.out.println("----方法执行之后-----");

    }

}
JDKProxy:

/**
 * @USER: 
 * @DATE: 2021-07-14
 * @description:Spring JDK 动态代理需要实现 InvocationHandler 接口,重写 invoke 方法,
 * 客户端使用 Java.lang.reflect.Proxy 类产生动态代理类的对象。
 */
public class JdkProxy implements InvocationHandler {

    private Object target; //需要代理的目标对象
    final MyAspect myAspect = new MyAspect();  //切面实例

    // 1.定义获取代理对象方法 客户端使用 Java.lang.reflect.Proxy 类产生动态代理类的对象。
    public Object JDKProxy(Object targetObject){
        this.target = targetObject;  // 为目标对象target赋值
        return  Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        myAspect.myBefore();//前置通知
        Object obj =  method.invoke(target,args);
        myAspect.myAfter();
        return obj;
    }

}
测试:
public class TestJdkProxy {

    @Test
    public void test(){
        JdkProxy jdkProxy = new JdkProxy();
        BookService service = (BookService) jdkProxy.JDKProxy(new BookServiceImpl());
        BookChildService bookChildService = (BookChildService) jdkProxy.JDKProxy(new BookServiceImpl());
        Book book = new Book(1,"老人与海","哈哈哈");
        service.selectById(1);
        service.selectAll();
        service.addBook(book);
        service.updateBook(book);
        System.out.println("-----------------------------------");
        bookChildService.selectCilidByid(1);

    }
}
测试结果

---方法执行之前-----
selectById正在执行根据ID查询Book数据@
----方法执行之后-----
---方法执行之前-----
selectAll正在执行查询所有数据!
----方法执行之后-----
---方法执行之前-----
addBook执行添加Book操作!
name=老人与海;author=哈哈哈
----方法执行之后-----
---方法执行之前-----
updateBook执行修改操作!
name=老人与海;author=哈哈哈
----方法执行之后-----
-----------------------------------
---方法执行之前-----
selectCilidByid查询数据
----方法执行之后-----

2. CGLIB动态代理

JDK 动态代理的目标类必须要实现一个或多个接口,具有一定的局限性,如果不希望实现接口,可以使用 CGLIB代理。

2.1 导入依赖

使用 CGLIB 需要导入 CGLIB 和 ASM 包,即 asm-x.x.jar 和 CGLIB-x.x.x.jar 。如果您已经导入了 Spring 的核心包 spring-core-x.x.x.RELEASE.jar,就不用再导入 asm-x.x.jar 和 cglib-x.x.x.jar 了。

 <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>5.1.8.RELEASE</version>
      <scope>compile</scope>
    </dependency>

2.2创建接口

public interface BookService {

    Book selectById(int id);
    List<Book> selectAll();
    void addBook(Book book);
    void updateBook(Book book);
}

2.3 编写实现类

public class BookServiceImpl implements BookService {
    @Override
    public Book selectById(int id) {
        System.out.println("selectById正在执行根据ID查询Book数据@");
        return null;
    }

    @Override
    public List<Book> selectAll() {
        System.out.println("selectAll正在执行查询所有数据!");
        return null;
    }

    @Override
    public void addBook(Book book) {
        System.out.println("addBook执行添加Book操作!");
        System.out.println("name="+book.getName()+";author="+book.getAuthor());
    }

    @Override
    public void updateBook(Book book) {
        System.out.println("updateBook执行修改操作!");
        System.out.println("name="+book.getName()+";author="+book.getAuthor());
    }

}

2.4 编写Aspect切面类

public class MyAspect {

    public void myBefore(){
        System.out.println("---方法执行之前-----");
    }
    public void myAfter(){
        System.out.println("----方法执行之后-----");

    }

}
2.5 编写CGLIB类
public class MyCglib  implements MethodInterceptor {

    private Object target;//目标对象
    final MyAspect myAspect = new MyAspect();//切面类

    /**
     * 定义代理对象方法
     * @param objectTarget
     * @return
     */
    public Object getCglibProxy(Object objectTarget){
        //为目标对象赋值
        this.target = objectTarget;
        Enhancer enhancer = new Enhancer();
        //设置父类,因为CGLIB是针对指定的类生成一个子类,所以需要指定父类
        enhancer.setSuperclass(objectTarget.getClass());
        //设置回调
        enhancer.setCallback(this);
        Object object = enhancer.create();//创建并返回代理对象
        return object;
    }

    /**
     * 重写拦截方法
     * @param o
     * @param method
     * @param objects
     * @param methodProxy
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
      myAspect.myBefore();
      Object obj =method.invoke(target,objects);
        myAspect.myAfter();
        return obj;
    }
}

2.6 测试
public class TestCglib {

    @Test
    public void test(){
        MyCglib cglib = new MyCglib();//实例化CglibProxy对象
       BookService service = (BookService) cglib.getCglibProxy(new BookServiceImpl());
        Book book = new Book(1,"老人与海","哈哈哈");
        service.selectById(1);
        service.selectAll();
        service.addBook(book);
        service.updateBook(book);
        System.out.println("-----------------------------------");

    }

}

3. JDK代理和CGLIB代理的区别

JDK 动态代理只能对实现了接口的类生成代理,而不能针对类。

CGLIB 是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。因为是继承,所以该类或方法不能声明成 final 类型。
JDK动态代理特点

  • 代理对象必须实现一个或多个接口
  • 以接口的形式接收代理实例,而不是代理类
    CGLIB动态代理特点
  • 代理对象不能被 final 修饰
  • 以类或接口形式接收代理实例
    JDK与CGLIB动态代理的性能比较
  • 生成代理实例性能:JDK > CGLIB
  • 代理实例运行性能:JDK > CGLIB

4. Spring集成AspectJ

4.1引入依赖
<dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.4</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.9</version>
        </dependency>
4.2 使用 AspectJ 开发 AOP 通常有以下 2 种方式:
  • 基于 XML 的声明式 AspectJ
  • 基于 Annotation 的声明式 AspectJ
4.2.1基于AspectJ XML开发

基于 XML 的声明式是指通过 Spring 配置文件的方式来定义切面、切入点及通知,而所有的切面和通知都必须定义在 aop:config 元素中。

编写通知类

public class Logging {

    /**
     * 前置通知
     */
    public void beforeAdvice(){
        System.out.println("前置通知");
    }

    /**
     * 后置通知
     */
    public void afterAdive(){
        System.out.println("后置通知");
    }

    /**
     * 返回后通知
     * @param retVal
     */
    public void afterReturnAdvice(Object retVal){
        System.out.println("返回值="+retVal);
    }

    /**
     * 抛出异常通知
     */
    public void afterThrowingAdvice(IllegalArgumentException e){
        System.out.println("这里的异常为:"+e.toString());

    }
}

编写实体类:

public class Person {
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    private String name;
    private int age;

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public void throwException(){
        System.out.println("抛出异常");
        throw new IllegalArgumentException();
    }

}

编写配置类:bean.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context  http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop  http://www.springframework.org/schema/aop/spring-aop.xsd">
    <bean id="person" class="com.yxkj.spring_aop_demo.entity.Person">
        <property name="name" value="hhhhhh"></property>
        <property name="age" value="22"></property>
    </bean>
    <bean id="logging" class="com.yxkj.spring_aop_demo.entity.Logging"></bean>

    <!--aop配置 -->
    <aop:config>
        <!--定义aop切面 -->
        <aop:aspect id="log" ref="logging">
            <!--定义切入点 -->
            <aop:pointcut id="myPointCut" expression="execution(* com.yxkj.spring_aop_demo.*.*.*(..))"/>
            <!--前置通知 -->
            <aop:before method="beforeAdvice" pointcut-ref="myPointCut" ></aop:before>
            <!-- 后置通知-->
            <aop:after-returning method="afterReturnAdvice" returning="retVal" pointcut-ref="myPointCut"></aop:after-returning>
            <!-- 异常通知-->
            <aop:after-throwing method="afterThrowingAdvice" pointcut-ref="myPointCut" throwing="e"></aop:after-throwing>
            <!--最终通知 -->
            <aop:after method="afterAdive" pointcut-ref="myPointCut"></aop:after>
        </aop:aspect>
    </aop:config>
</beans>

测试:

public class TestAopXml {

    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
       Person person = context.getBean("person",Person.class);
        String name = person.getName();
        int  age =  person.getAge();
       // System.out.println(name+":"+age);
       //person.throwException();
    }
}


4.2.2 定义切面aop:aspect
4.2.3 定义切入点aop:pointcut

aop:pointcut 用来定义一个切入点,当 aop:pointcut元素作为 aop:config 元素的子元素定义时,表示该切入点是全局切入点,它可被多个切面所共享;
aop:pointcut 元素作为 aop:aspect 元素的子元素时,表示该切入点只对当前切面有效。

<aop:config>
    <aop:pointcut id="myPointCut"
        expression="execution(* net.biancheng.service.*.*(..))"/>
</aop:config>

其中,id 用于指定切入点的唯一标识名称,execution 用于指定切入点关联的切入点表达式。
execution 格式为:

execution(modifiers-pattern returning-type-pattern declaring-type-pattern name-pattern(param-pattern)throws-pattern)

其中:

  • returning-type-pattern、name-pattern、param-pattern 是必须的,其它参数为可选项。
  • modifiers-pattern:指定修饰符,如 private、public。
  • returning-type-pattern:指定返回值类型,*表示可以为任何返回值。如果返回值为对象,则需指定全路径的类名。
  • declaring-type-pattern:指定方法的包名。
  • name-pattern:指定方法名,代表所有,set 代表以 set 开头的所有方法。
  • param-pattern:指定方法参数(声明的类型),(..)代表所有参数,()代表一个参数,(,String)代表第一个参数可以为任何值,第二个为 String 类型的值。
  • throws-pattern:指定抛出的异常类型。

定义通知:

<aop:aspect id="myAspect" ref="aBean">
    <!-- 前置通知 -->
    <aop:before pointcut-ref="myPointCut" method="..."/>
   
    <!-- 后置通知 -->
    <aop:after-returning pointcut-ref="myPointCut" method="..."/>
    <!-- 环绕通知 -->
    <aop:around pointcut-ref="myPointCut" method="..."/>
    <!-- 异常通知 -->
    <aop:after-throwing pointcut-ref="myPointCut" method="..."/>
    <!-- 最终通知 -->
    <aop:after pointcut-ref="myPointCut" method="..."/>
    .... 
</aop:aspect>

4.3 AspectJ基于注解开发AOP

AspectJ 框架为 AOP 开发提供了一套注解。
注解如下:
image
启用 @AspectJ 注解有以下两种方法:
1)使用@Configuration和@EnableAspectJAutoProxy注解

@Configuration 
@EnableAspectJAutoProxy
public class Appconfig {
}

2)基于XML配置

<aop:aspectj-autoproxy>

定义切面@Aspect

@Aspect
public class AspectModule {
}

定义切入点@Pointcut

// 要求:方法必须是private,返回值类型为void,名称自定义,没有参数
@Pointcut("execution(*net.biancheng..*.*(..))")
private void myPointCut() {
}

定义通知advice

@Before("myPointCut()")
    public void beforeAdvice(){
        System.out.println("前置通知");
    }
    @AfterReturning(pointcut = "myPointCut()",returning = "retVal")
    public void afterRetrunAdive(Object retVal){
        System.out.println("后置通知="+retVal.toString());
    }
    @After("myPointCut()")
    public void afterAdive(){
        System.out.println("最终通知");
    }


代码全部:

1.定义自动引入aop代理注解

@SpringBootApplication
@EnableAspectJAutoProxy
public class SpringAopDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringAopDemoApplication.class, args);
    }

}

2.定义切面类:

@Aspect
@Component
public class AopAspectConfig {

    @Pointcut("execution(* com.yxkj.spring_aop_demo.*.*.*(..))")
    public void myPointcut(){

    }

    @Before("myPointcut()")
    public void beforeAdive(){
        System.out.println("beforeAdive前置通知");
    }
    @AfterReturning(pointcut = "myPointcut()",returning="object")
    public void afterReturnAdvice(Object object){
        System.out.println("后置返回通知="+object);
    }

    @After("myPointcut()")
    public void afterAdvice(){
        System.out.println("最终通知");
    }
    @AfterThrowing(value = "myPointcut()",throwing = "e")
    public void throwingAdivce(Exception e){
        System.out.println("异常通知!");
        e.getMessage();
    }
}

3.测试:
写一个controller

@RestController
@RequestMapping(value = "/login")
public class LongUserController {

    @RequestMapping(value = "/index",method = RequestMethod.POST)
    public String login(){
        System.out.println("哈哈哈哈哈");
        return "登录成功!";
    }

    @RequestMapping(value = "/error",method = RequestMethod.GET)
    public String error(){
        System.out.println("登录失败!");
        return "登录失败!";
    }
}

访问登录成功后的控制台:

beforeAdive前置通知
哈哈哈哈哈
最终通知
后置返回通知=登录成功!

posted @ 2021-07-14 15:17  风飘落叶  阅读(145)  评论(0编辑  收藏  举报