初识AOP

(1)什么是AOP

  AOP (Aspect Orient Programming),直译过来就是 面向切面编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。

  用刀把一个西瓜分成两瓣,切开的切口就是切面;炒菜,锅与炉子共同来完成炒菜,锅与炉子就是切面。web层级设计中,web层->网关层->服务层->数据层,每一层之间也是一个切面。编程中,对象与对象之间,方法与方法之间,模块与模块之间都是一个个切面。

(2)为什么用AOP

  就是为了方便,一个国外很有名的大师说,编程的人都是“懒人”,因为他把自己做的事情都让程序做了,用了AOP能让你少写很多代码。 为了更清晰的逻辑,可以让你的业务逻辑去关注自己本身的业务,而不去想一些其他的事情,这些其他的事情包括:安全,事物,日志等。

(3)AOP的引入:以代理模式为例

静态代理

 a. 定义业务接口

public interface IAccountService {
    // 转账业务接口
    void transfer();
}

b、定义目标类与目标方法  

public class AccountServiceImpl implements IAccountService {
    // 转账业务实现即目标方法
    @Override
    public void transfer() {
        System.out.println("进行转账操作");
    }
}

c. 定义代理类AccountServiceImplProxy,实现IAccountService接口。在有参构造方法中传入目标对象,将目标对象引入代理类,以便代理类调用目标方法,进行增强

public class AccountServiceImplProxy implements IAccountService {

    // 声明目标接口对象
    private IAccountService target;

    public AccountServiceImplProxy() {
    }
    // 业务接口对象作为构造器,用于接收目标对象
    public AccountServiceImplProxy(IAccountService target) {
        this.target = target;
    }

    @Override
    public void transfer() {
        // 此处对目标方法进行增强
        System.out.println("对转账人身份校验。。");
        target.transfer();
        System.out.println("进行日志记录。。");
    }
}

d. 编写测试类TransferServiceTest 

public class TransferServiceTest {
    public static void main(String[] args) {
        // 创建目标对象
        IAccountService target = new AccountServiceImpl();
        // 创建代理对象,传入目标对象进行初始化
        IAccountService proxy = new AccountServiceImplProxy(target);
        // 执行代理对象的方法
        proxy.transfer();
    }
}

动态代理

动态代理,程序在整个运行过程中根本就不存在目标类的代理类,目标对象的代理对象只是由代理工具在程序运行时由JVM根据反射等机制动态生成。代理对象与目标对象的代理关系在程序运行时才确立。

(1)JDK动态代理

     JDK动态代理是通过JDK提供的 java.lang.reflect.Proxy类实现动态大力,使用其静态方法newProxyInstance(),对目标对象、业务接口及业务增强逻辑,自动生成一个动态代理对象。

    1. 定义业务接口与实现类

public interface IAccountService {
    // 转账业务接口
    void transfer();
}

 

public class AccountServiceImpl implements IAccountService{
    public void transfer() {
        System.out.println("进行转账操作");
    }
}

 2. 定义JdkProxy类实现InvocationHandler接口,实现invoke()方法,并对业务逻辑进行增强

public class JdkProxy implements InvocationHandler {

    private Object target;

    public JdkProxy() {
    }

    public JdkProxy(Object target) {
        this.target = target;
    }


    /*
      proxy:生成的代理对象
	  method:目标方法
	  args:目标方法的参数
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 此处对目标方法进行增强
        System.out.println("对转账人身份校验。。");
        //执行目标方法
        /*
            target:目标对象
            args:目标方法的执行参数
         */
        Object result = method.invoke(target, args);
        System.out.println("进行日志记录。。");
        return result;
    }
}

 3. 测试验证

public class JDKProxyTest {
    public static void main(String[] args) {
        // 创建目标对象
        IAccountService target = new AccountServiceImpl();
        // 创建代理对象,传入目标对象进行初始化
        /*
            public static newProxyInstance(ClassLoader classLoader,Class<?> interfaces,InvocationHandler handler)
            classLoader:传入目标类的类加载器,通过目标对象的反射获取
            interfaces:目标对象实现的接口数组,通过目标对象的反射获取
            handler:业务增强逻辑,需要具体实现
         */
        IAccountService proxyService =
                (IAccountService) Proxy.newProxyInstance(
                        target.getClass().getClassLoader(),
                        target.getClass().getInterfaces(),
                        new JdkProxy(target)
                );

        proxyService.transfer();
    }
}

(2)CGLIB动态代理

      CGLIB是一个开源的第三方代码生成类库,对于无接口的类,要为其创建动态代理,就要使用CGLIB进行实现。CGLIB代理的生成原理是生成目标类的子类,子类是增强过的,就是目标类的代理类。所以,使用CGLIB生成动态代理,要求目标类必须能够被继承,即不能是final修饰的类。

1.引入CGLIB依赖 

<dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib-full</artifactId>
        <version>2.0.2</version>
</dependency>

2.创建目标类AccountService  

public class AccountService  {

    // 转账业务 即目标方法
    public void transfer() {
        System.out.println("进行转账操作");
    }

    // 查询余额 即目标方法
    public void getBalance() {
        System.out.println("查询余额操作");
    }
}

3. 创建代理类AccountServiceCglibProxy实现MethodInterceptor接口,完善intercept()方法进行增强,创建生成代理对象createProxy()方法  

public class AccountServiceCglibProxy implements MethodInterceptor {

    // 声明目标类的成员变量,并创建以目标类为参数的构造器,用于接收目标对象
    private AccountService target;

    public AccountServiceCglibProxy(AccountService accountService) {
        this.target = accountService;
    }


    /**
     * @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 {
        // 此处对目标方法进行增强
        Object result = new Object();
        if ("transfer".equals(method.getName())){
            System.out.println("对转账人身份校验。。");
            result = method.invoke(target, objects);
            System.out.println("进行日志记录。。");
        }else{
            // 直接执行目标对象的目标方法
            result = methodProxy.invokeSuper(o, objects);
        }
        return result;
    }

    // 创建代理对象
    public AccountService createProxy(){
        // 创建增强器
        Enhancer enhancer = new Enhancer();
        // 初始化增强器:将目标类指定为父类
        enhancer.setSuperclass(AccountService.class);
        // 初始化增强器:设置回调至本类中的intercept()方法
        enhancer.setCallback(this);
        // 使用增强器创建代理对象
        return (AccountService) enhancer.create();
    }

}

4. 创建测试类

public class CglibProxyTest {
    public static void main(String[] args) {
        // 目标对象
        AccountService target = new AccountService();
        // 创建代理对象,传入目标对象进行初始化
        AccountService accountService = (AccountService) new AccountServiceCglibProxy(target).createProxy();
        accountService.transfer();
        accountService.getBalance();
    }
}

Spring中的aop

  Spring中的AOP,就是通过配置的方式(有基于XML配置的, 以及基于注解配置的),来实现相关的拦截切入功能。对原有的操作进行加强,但不影响原本的操作。

       因为Spring AOP中的代理对象由IoC容器自动生成,所以开发者无须过多关注代理对象生成的过程,只需选择连接点、创建切面、定义切点并在XML文件中添加配置信息即可。 Spring提供了一系列配置Spring AOP的XML元素。

       配置Spring AOP的XML元素

 测试案例

1、引入pom

  <properties>
    <java.version>8</java.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>4.0.0.RELEASE</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>4.0.0.RELEASE</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>4.0.0.RELEASE</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
    <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.1.1</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>compile</scope>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>4.0.0.RELEASE</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/cglib/cglib -->
    <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>2.2.2</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.8.13</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
    <dependency>
      <groupId>aopalliance</groupId>
      <artifactId>aopalliance</artifactId>
      <version>1.0</version>
    </dependency>
  </dependencies>

  

2、创建目标类

public class StudentService {


    public void eat(){
        System.out.println("我要开始吃午饭了");
    }

}

3、创建切面类

public class StudentAspect {

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

    // 后置通知:
    public void after() {
        System.out.println("后置通知============");
    }

    // 环绕通知:
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕前通知==========");
        Object obj = joinPoint.proceed();
        System.out.println("环绕后通知==========");
        return obj;
    }

    // 异常抛出通知:
    public void afterThrowing(Throwable e) {
        System.out.println("异常抛出通知=========" + e.getMessage());
    }

    //返回通知
    public void afterReturning(Object result) {
        System.out.println("返回通知===========" + result);
    }

}

4、创建配置文件

 

  在Spring的配置文件中,配置切面使用的是<aop:aspect>元素,该元素会将一个已定义好的Spring Bean转换成切面Bean,因此,在使用<aop:aspect>元素之前,要在配置文件中先定义一个普通的Spring Bean。Spring Bean定义完成后,通过<aop:aspect>元素的ref属性即可引用该Bean。

切点表达式类型execution

匹配方法切入点。根据表达式描述匹配方法,是最通用的表达式类型,可以匹配方法、类、包。

表达式模式:

execution(modifier? ret-type declaring-type?name-pattern(param-pattern) throws-pattern?)
表达式解释:

(1)modifier:匹配修饰符,public, private 等,省略时匹配任意修饰符

(2)ret-type:匹配返回类型,使用 * 匹配任意类型

(3)declaring-type:匹配目标类,省略时匹配任意类型

  • .. 匹配包及其子包的所有类

(4)name-pattern:匹配方法名称,使用 * 表示通配符

  • * 匹配任意方法
  • set* 匹配名称以 set 开头的方法

(5)param-pattern:匹配参数类型和数量

  • () 匹配没有参数的方法
  • (..) 匹配有任意数量参数的方法
  • (*) 匹配有一个任意类型参数的方法
  • (*,String) 匹配有两个参数的方法,并且第一个为任意类型,第二个为 String 类型

(6)throws-pattern:匹配抛出异常类型,省略时匹配任意类型

使用示例

<?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: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/aop http://www.springframework.org/schema/aop/spring-aop.xsd">


    <!-- 配置目标类 -->
    <bean id="studentService" class="com.gqx.StudentService"></bean>

    <!-- 配置切面类 -->
    <bean id="studentAspectXML" class="com.gqx.StudentAspect"></bean>

    <!-- 配置 AOP -->
    <aop:config>
    <!-- 配置切点表达式 -->
    <!--
       整个表达式可以分为五个部分:
       1、execution()::表达式主体。
       2、第一个*号:表示返回类型, *号表示所有的类型。
       3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service包、子孙包下所有类的方法。
       4、第二个*号:表示类名,*号表示所有的类。
       5、*(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数
    -->
    <!-- 配置切点 -->
    <aop:pointcut expression="execution(* com.gqx.StudentService.eat(..))" id="pointcut1" />

    <!-- 配置切面及通知 -->
    <aop:aspect ref="studentAspectXML">
        <!-- 前置通知 -->
        <aop:before method="before" pointcut-ref="pointcut1" />
        <!-- 后置通知 -->
        <aop:after method="after" pointcut-ref="pointcut1" />
        <!--     环绕通知-->
<!--        <aop:around method="around" pointcut-ref="pointcut3" />-->

        <!-- 异常抛出通知 -->
<!--        <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut4" throwing="e" />-->
        <!--  返回通知  -->
<!--        <aop:after-returning method="afterReturning" pointcut-ref="pointcut4" returning="result" />-->
    </aop:aspect>

    </aop:config>

</beans>

5、创建运行类

public class AppMain {

    public static void main(String[] args) {
        //1. 创建IoC容器对象,加载spring核心配置文件
        //初始化Spring容器ApplicationContext,加载配置文件
        ApplicationContext application = new ClassPathXmlApplicationContext("applicationContext.xml");
        //2. 从IOC容器中获取Bean对象
        StudentService studentService = (StudentService) application.getBean("studentService");
        studentService.eat();
    }
}

练习

补充下面接口测试代码,实现基于AOP的切面编程。

(1)创建接口UserDao,并在该接口中编写添加、删除、修改和查询的方法。

public interface UserDao {
    public void insert();
    public void delete();
    public void update();
    public void select();
}

创建UserDao接口的实现类UserDaoImpl,实现UserDao接口中的方法。

public class UserDaoImpl implements UserDao{
    public void insert() {
        System.out.println("添加用户信息"); }
    public void delete() {
        System.out.println("删除用户信息"); }
    public void update() {
        System.out.println("更新用户信息"); }
    public void select() {
        System.out.println("查询用户信息"); }
}

(2)创建XmlAdvice类,用于定义切面通知。

public class XmlAdvice {
    // 前置通知
    public void before(JoinPoint joinPoint){
        System.out.print("这是前置通知!");
        System.out.print("目标类是:"+joinPoint.getTarget());
        System.out.println(",被织入增强处理的目标方法为:"+
                                joinPoint.getSignature().getName());
    }
    //需要补充:返回通知、环绕通知、异常通知、后置通知
}

(3)创建applicationContext.xml文件,在该文件中引入AOP命名空间,使用<bean>元素添加Spring AOP的配置信息。

<!-- 注册bean省略,下面内容为配置Spring AOP-->
<aop:config>
     <aop:pointcut id="pointcut" expression=""/><!-- 指定切点 -->
     <aop:aspect ref ="xmlAdvice"><!-- 指定切面 -->
         <aop:before method="before" pointcut-ref="pointcut"/><!-- 指定前置通知 -->
         <aop:after-returning method="afterReturning" pointcut-ref="pointcut"/>
         <aop:around method="around" pointcut-ref="pointcut"/>-- 指定环绕方式 -->
         <aop:after-throwing method="afterException" pointcut-ref="pointcut"/>
         <aop:after method="after" pointcut-ref="pointcut"/><!-- 指定后置通知 -->
    </aop:aspect>
</aop:config> 

(4)创建测试类TestXml,测试基于XML的AOP实现。  

public class TestXml{
    public static void main(String[] args){
        ApplicationContext context=new  ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao userDao=context.getBean("userDao",UserDao.class);
        userDao.delete();	 
        userDao.insert();	
        userDao.select();	
        userDao.update();	
    }
}  

结果如图:

 

 

  

  

  

posted @ 2023-03-06 00:05  晓乎  阅读(299)  评论(0编辑  收藏  举报
总访问: counter for blog 次