Spring学习(二)

AOP面向切面编程

概述:面向切面编程从动态角度考虑程序运行的过程。AOP底层,就是采用动态代理模式实现的,采用了两种代理:JDK的动态代理,与CGLIB的动态代理。AOP可通过运行期动态代理实现程序功能的统一维护的一种技术。AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,提高开发效率。

面向切面编程,就是将交叉业务逻辑封装成切面,利用AOP容器的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码。

Spring支持的AOP实现

Before通知:在目标方法被调用前调用,涉及接口org.springframework.aop.MethodBeforeAdvice;

After通知:在目标方法被调用后调用,涉及接口org.springframework.aop.AfterReturningAdvice;

Throws通知:目标方法抛出异常时调用,涉及接口org.springframework.aop.ThrowsAdvice;

Rround通知:拦截对目标对象方法调用,涉及接口为org.aopalliance.intercept.MethodInterceptor。

AOP常用的术语

切面:就是重复的,公共的,通用的功能称为切面,例如:日志,事务,权限

连接点:就是目标方法,因为在目标方法中要实现目标方法的功能和切面功能

切入点:多个连接点构成切入点,切入点可以是一个目标方法,也可以是一个类中的所有方法,可以是某个包下的所有类的方法

目标对象:操作谁,谁就是

通知:来指定切入的时机,是在目标方法执行前还是执行后,或者出错时,还是环绕目标方法切入切面功能。

AspectJ框架

一个优秀的面向对面的框架,它扩展了java语言,提供强大的切面实现,基于java语言开发,所以能够无缝扩展,且框架易学易用

常用通知

前置通知@Before 

后置通知@AfterReturning

环绕通知@Around 

最终通知@After 

定义切入点@Pointcut

AspectJ切入表达式(必须掌握)

规范的公式:

execution(访问权限 方法返回值 方法声明(参数) 异常类型)

简化后的公式:

execution(方法返回值 方法声明(参数))

会用到的符号:

* 代表任意个任意字符

.. 如果出现在方法中,则代表任意参数;如果在路径中,则代表本路径极其所有的子路径

例如execution(public * * (..)) 代表公共权限下的任意返回值的任意方法的任意参数

AspectJ的前置通知(@Before)

在目标方法执行前切入切面功能,在切面方法中不可以获得目标方法的返回值,只能得到目标方法的签名( 访问权限 方法返回值 方法声明(参数)

pom.xml,添加依赖为项目作准备

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>org.example</groupId>
  <artifactId>Spring_AspectJ</artifactId>
  <version>1.0-SNAPSHOT</version>

  <name>Spring_AspectJ</name>

  <properties>
    <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>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
<!--      Spring 依赖-->
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-context</artifactId>
          <version>5.2.5.RELEASE</version>
      </dependency>
<!--      AspectJ 依赖-->
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-aspects</artifactId>
          <version>5.2.5.RELEASE</version>
      </dependency>
  </dependencies>

  <build>
      <resources>
          <resource>
              <directory>src/main/java</directory>
              <includes>
                  <include>**/*.properties</include>
                  <include>**/*.xml</include>
              </includes>
              <filtering>false</filtering>
          </resource>
      </resources>
      <plugins>
          <plugin>
              <artifactId>maven-compiler-plugin</artifactId>
              <version>3.1</version>
              <configuration>
                  <source>1.8</source>
                  <target>1.8</target>
              </configuration>
          </plugin>
      </plugins>
  </build>
</project>

创建业务类接口,创建方法

package ys.com.Before;
//业务接口
public interface SomeService {
    default String doSome(String name,int age){return name;}

}

创建业务实现类,实现接口方法

package ys.com.Before;
//业务实现类
public class SomeServiceImpl implements SomeService{
    @Override
    public String doSome(String name, int age) {
        System.out.println("前置目标方法被实现了");
        return name+"今年:"+age;
    }
}

创建Spring配置文件,并进行对象创建,以及绑定切面和对象

<?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 https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--        创建业务对象-->
    <bean id="someService" class="ys.com.Before.SomeServiceImpl"></bean>
<!--    创建切面对象-->
    <bean id="aop" class="ys.com.Before.Aop"></bean>
<!--    绑定切面-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

主方法,输出

package ys.com.Main;

import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import ys.com.Before.SomeService;
import ys.com.Before.SomeServiceImpl;

public class myApp {
    public static void main(String[] args) {


//    启动Spring容器
    ApplicationContext applicationContext=new ClassPathXmlApplicationContext("Before/contxtUers.xml");
//  取出对象
    SomeService service =(SomeService) applicationContext.getBean("someService");
    String s =service.doSome("张三",22);
    System.out.println(s);
    }

}

JDK动态代理和CGLib动态代理的切换

Spring配置文件中,默认为JDK动态代理,此时只能用接口创建对象接受getBean值

下面是转化为CGLib动态代理---可以使用接口实现类对象,接口也是可以。

并将项目改为了用注解创建对象

对于上述代码修改,主方法,使用接口实现类创建对象

package ys.com.Main;

import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import ys.com.Before.SomeService;
import ys.com.Before.SomeServiceImpl;

public class myApp {
    public static void main(String[] args) {


//    启动Spring容器
    ApplicationContext applicationContext=new ClassPathXmlApplicationContext("Before/contxtUers.xml");
//  取出对象
    SomeServiceImpl service =(SomeServiceImpl) applicationContext.getBean("someService");
    String s =service.doSome("张三",22);
    System.out.println(s);
    }

}

再修改Spring配置文件

<?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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--        创建业务对象-->
<!--    <bean id="someServiceImpl" class="ys.com.Before.SomeServiceImpl"></bean>-->
<!--    创建切面对象-->
<!--    <bean id="aop" class="ys.com.Before.Aop"></bean>-->

<!--    包扫描,检索注解-->
    <context:component-scan base-package="ys.com.Before"></context:component-scan>
<!--    绑定切面-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

 对切面方法添加参数,可以获取目标方法的签名和参数

package ys.com.Before;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

import java.util.Arrays;

//切面类,包含切面方法
@Aspect //交给AspectJ的框架去识别切面类
@Component //使用注解由Spring创建对象
public class Aop implements SomeService{
//    所有的切面的功能都是由切面方法来实现的
//    前置通知的切面方法规范
//    1、访问权限是public
//    2、方法无返回值(void)
//    3、方法名称自定义
//    4、方法没有参数,如果有只能是JoinPoint类型
//    5、必须是有@Before注解来声明切入的时机和切入点
//    参数 value:指定切入点表达式
    @Before("execution(String doSome(String,int))")
    public void myBefore(){
        System.out.println("前置通知功能实现了");
    }
    @Before("execution(String doSome(String,int))")
    public void joinPoint(JoinPoint jp){
        System.out.println("目标方法的签名:"+jp.getSignature());
//        jp.getArgs()是数组的形式  使用Arrays.toString可以输出
        System.out.println("目标方法的参数:"+ Arrays.toString(jp.getArgs()));
    }
}
package ys.com.Before;

import org.springframework.stereotype.Service;

//业务实现类
@Service //使用注解由Spring创建对象
public class SomeServiceImpl implements SomeService{
    @Override
    public String doSome(String name, int age) {
        System.out.println("前置目标方法被实现了");
        return name+"今年:"+age;
    }
}

AspectJ的后置通知(@AfterReturning)

在目标方法执行后切入切面功能,在切面方法中可以获得目标方法的返回值,可以修改目标方法的返回值,但是目标方法的返回值不能为八大基本类型和String,引用类型可以被修改。

创建接口实现类

package ys.com.AfterReturning;

import org.springframework.stereotype.Component;

@Component
public class Student {
    private String name;

    public Student(String name) {
        this.name = name;
    }

    public Student() {
    }

    public String getName() {
        return name;
    }

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

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

 创建接口

package ys.com.AfterReturning;
//业务接口
public interface SomeService {
    default String doSome(String name,int age){return name;}
    public Student doStudent();
}

创建接口实现类

package ys.com.AfterReturning;

import org.springframework.stereotype.Service;

//业务实现类
@Service //使用注解由Spring创建对象
public class SomeServiceImpl implements SomeService {
    @Override
    public String doSome(String name, int age) {
        System.out.println("后置目标方法被实现了");
        return name+"今年:"+age;
    }

    @Override
    public Student doStudent() {
        System.out.println("学生类");

        return new Student("张三");
    }
}

创建Spring配置文件

<?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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--        创建业务对象-->
<!--    <bean id="someServiceImpl" class="ys.com.Before.SomeServiceImpl"></bean>-->
<!--    创建切面对象-->
<!--    <bean id="aop" class="ys.com.Before.Aop"></bean>-->

<!--    包扫描,检索注解-->
    <context:component-scan base-package="ys.com.AfterReturning"></context:component-scan>
<!--    绑定切面-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

定义主方法

package ys.com.Main;


import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import ys.com.AfterReturning.SomeService;
import ys.com.AfterReturning.Student;


public class myApp {
    public static void main(String[] args) {


//    启动Spring容器
    ApplicationContext applicationContext=new ClassPathXmlApplicationContext("AfterReturning/contxtUers.xml");
//  取出对象
    SomeService service =(SomeService) applicationContext.getBean("someServiceImpl");
        Student s=service.doStudent();
    System.out.println("主方法中的返回值为"+s);
    }

}

输出结果,在接口实现类的重写方法中,返回值为“张三”,通过切面方法修改后变为了“李四”

 AspectJ的环绕通知(@Around)

通过拦截目标方法的方式,在目标方法前后增强功能的通知。是功能强大的通知,一般事务使用此通知。可以轻易的改变目标方法的返回值

接口方法

package ys.com.Around;
//业务接口
public interface SomeService {
   String doSome(String name,int age);

}

接口实现类

package ys.com.Around;

import org.springframework.stereotype.Service;

//业务实现类
@Service //使用注解由Spring创建对象
public class SomeServiceImpl implements SomeService {
    @Override
    public String doSome(String name, int age) {
        System.out.println("目标方法被实现了"+name+"今年:"+age);
        return "abcd";
    }
}

切面方法

package ys.com.Around;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import java.util.Locale;

//切面类,包含切面方法
@Aspect //交给AspectJ的框架去识别切面类
@Component //使用注解由Spring创建对象
public class Aop{
//    环绕通知的规范
//    访问权限是public
//    切面方法有返回值,此返回值就是目标方法的返回值
//    方法名自定义
//    方法有参数,此参数就是目标方法
//    回避异常
//    使用@Around注解声明是环绕通知
//   value:指定切入点表达式
    @Around(value = "execution(String  doSome(..))")
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//        ProceedingJoinPoint 函数的参数类型
//        前切功能
        System.out.println("前");
//        目标方法
//        .proceed调用函数 .getArgs()获取所有参数类型的的函数,包括无参
            Object object=proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
//        后切功能
        System.out.println("后");
     return  object.toString().toUpperCase(Locale.ROOT);
//     toString()强转化类型为字符串  toUpperCase将字符串的内容大写
    }
}

Spring配置文件

<?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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--        创建业务对象-->
<!--    <bean id="someServiceImpl" class="ys.com.Before.SomeServiceImpl"></bean>-->
<!--    创建切面对象-->
<!--    <bean id="aop" class="ys.com.Before.Aop"></bean>-->

<!--    包扫描,检索注解-->
    <context:component-scan base-package="ys.com.Around"></context:component-scan>
<!--    绑定切面-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

主方法

package ys.com.Main;


import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import ys.com.Around.SomeService;


public class myApp {
    public static void main(String[] args) {


//    启动Spring容器
    ApplicationContext applicationContext=new ClassPathXmlApplicationContext("Around/contxtUers.xml");
//  取出对象
    SomeService service =(SomeService) applicationContext.getBean("someServiceImpl");
    String s=service.doSome("张三",22);
    System.out.println("主方法中的返回值为"+s);
    }

}

 AspectJ的最终通知(@After)

无论目标方法是否正常执行,最终通知都会被执行。

接口方法与接口实现类与前面相同(省略)

切面方法

package ys.com.After;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import java.util.Locale;

//切面类,包含切面方法
@Aspect //交给AspectJ的框架去识别切面类
@Component //使用注解由Spring创建对象
public class Aop{
//    最终通知的规范
//    访问权限是public
//    方法没有返回值
//    方法名自定义
//    方法没有参数,如果有只能是JoinPoint
//    使用@After注解声明是最终通知
//   value:指定切入点表达式
    @After(value = "execution(String  doSome(..))")
    public void myAround() {
        System.out.println("最终通知的功能输出......");

    }
}

Spring配置文件

<?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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--        创建业务对象-->
<!--    <bean id="someServiceImpl" class="ys.com.Before.SomeServiceImpl"></bean>-->
<!--    创建切面对象-->
<!--    <bean id="aop" class="ys.com.Before.Aop"></bean>-->

<!--    包扫描,检索注解-->
    <context:component-scan base-package="ys.com.After"></context:component-scan>
<!--    绑定切面-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

主方法

package ys.com.Main;


import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import ys.com.After.SomeService;


public class myApp {
    public static void main(String[] args) {


//    启动Spring容器
    ApplicationContext applicationContext=new ClassPathXmlApplicationContext("After/contxtUers.xml");
//  取出对象
    SomeService service =(SomeService) applicationContext.getBean("someServiceImpl");
    String s=service.doSome("张三",22);
    System.out.println("主方法中的返回值为"+s);
    }

}

当多个通知存在时,从上到下的输出顺序为

环绕通知的前置通知

前置通知

目标方法

环绕通知的后置通知

最终通知

后置通知

测试方法的输出

@Pointcut 定义切入点别名

       当较多的通知增强方法使用相同的execution切入点表达式时,编写、维护均较为麻烦。AspectJ提供了@Pointcut注解,用于定义 execution切入点表达式。其用法是,将@Pointcut注解在一个方法之上,以后所有的 execution 的 value 属性值均可使用该方法名作为切入点。代表的就是@Pointcut定义的切入点。这个使用@Pointcut注解的方法一般使用 private的标识方法,即没有实际作用的方法。

   如果多个切面切入同一个切入点,可以使用别名简化开发

 使用@Pointcut注解,创建一个空方法,此方法的名称就是别名。

将前置通知中的切面方法改造为

package ys.com.Pointcut;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

import java.util.Arrays;

//切面类,包含切面方法
@Aspect //交给AspectJ的框架去识别切面类
@Component //使用注解由Spring创建对象
public class Aop implements SomeService {
    @Pointcut(value = "execution(String doSome(String,int))")
    public void Pointcut(){}
    @Before("Pointcut()")
    public void myBefore(){
        System.out.println("前置通知功能实现了");
    }
    @Before("Pointcut())")
    public void joinPoint(JoinPoint jp){
        System.out.println("目标方法的签名:"+jp.getSignature());
//        jp.getArgs()是数组的形式  使用Arrays.toString可以输出
        System.out.println("目标方法的参数:"+ Arrays.toString(jp.getArgs()));
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值