文章目录
1.动态代理 2.AOP
1.动态代理
1. 实现方式:
- JDK动态代理,使用jdk中的Proxy,Method,InvocationHandler创建代理对象,要求目标类必须实现接口
- cglib动态代理,第三方的工具库,原理是继承。通过继承目标类创建子类,子类就是代理对象,要求目标类和方法不是final的
2. 动态代理的作用:
- 在目标类原代码不改变的情况下,为其增加功能
- 减少代码的重复
- 专注业务逻辑代码
- 解耦合,让业务功能和日志、事务等非业务功能分离
2.AOP
1. what‘s AOP?
- AOP:Aspect Oriented Programming,面向切面编程。切面指的是为目标类增加的功能,比如日志、事务等,其特点是非业务方法,独立使用。
- 如何理解面向切面编程呢,如何面向?
- 在分析项目功能的时候找出切面
- 合理地安排切面的执行位置,哪个类?哪个方法?
- 合理地安排切面的执行时间,方法前?方法后?
- AOP是基于动态代理来实现的,就是动态代理的规范化,把动态代理的实现步骤,实现方式都定义好了,让开发人员用一种统一的方式,非常方便地实现动态代理,非常方便的在原有业务逻辑的基础上增加功能而无需改动原来的代码。
2. AOP相关的术语
- Advice:通知或者叫增强,表示增强的功能,是咱们写的某个方法,这个方法同时包含着执行时间这个信息
- JoinPoint:连接点,所有可以加通知的地方都是
- PonitCut:切入点,就是咱们真的加通知的地方,是连接点的子集,就是某些方法处
- Aspect:切面,就是通知和切入点相结合的一个概念,包含了三层意思在哪里(切入点),干什么、什么时候干(通知)
- 目标对象:给哪个类的方法增加功能,这个类就是目标对象
3.AOP的实现
- SpringAOP:Spring内部实现的AOP
- AspectJ:eclipse开源的AOP框架,Spring中集成了该框架,可以用xml和注解两种方式使用AspectJ,我们一般都使用注解,AspectJ有5个注解。在配置全局事务的时候会用xml方式
4.AspectJ的介绍
(1) 5个通知注解:
- 标注在通知方法上面,让这个方法同时具备干什么和什么时间干两重意义
@Before
@AfterReturning
@Around
AfterThrowing
After
(2) 切入点表达式
- 写在5个通知注解的属性内,告诉通知方法切入点是哪里,即在哪里干
- 切入点表达式的写法:访问权限和异常类型可以省略
execution(访问权限 方法返回值 方法声明(参数) 异常类型)
- 切入点表达式的举例
execution(public * *(..))
//指定切入点为任意公共方法
execution(* set*(..))
//指定切入点为任何一个以set开头的方法
execution(* xyz.guawaz.service.*.*(..))
//指定xyz.guawaz.service包下的所有类的所有方法为切入点,(不包括service的子包中的类的方法哦)
execution(* xyz.guawaz.service..*.*(..))
//指定在service包或者子包里的所有类的所有方法为切入点
execution(* *..service.*.*(..))
//指定所有包下的service子包下的所有类中所有方法为切入点(service前可以有任意层包)
5.AspectJ的使用(以@Before为例)
- 使用aspectj框架实现aop
- 使用aop给已经存在的一些方法增加功能,前提是不改变原来的类的代码
- 基本步骤:
- 新建maven项目
- 加入依赖(spring依赖+aspectj依赖+junit单元测试)
- 创建目标类 包括接口和实现类
- 创建切面类
- 4.1 在类的上面加@Aspect注解表明这是个切面类
- 4.2 在类中定义方法,这些方法就是切面要执行的功能代码
- 4.3 在方法上面加入aspectj中的通知方法并标明切入点表达式execution()
- 创建spring的配置文件,声明对象,将对象交给容器统一管理
- 5.1 声明目标对象
- 5.2 声明切面类对象
- 5.3 声明aspectj中的自动代理生成器标签:用来完成代理对象的自动创建
- 创建测试类,从spring容器中获取目标对象(实际上是代理对象),执行方法
1.新建maven项目
2.加入相关依赖
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.9</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.9</version>
</dependency>
</dependencies>
3.创建目标类
//SomeService.java
package xyz.guawaz.ba01;
public interface SomeService {
void doSome(String name,Integer age);
}
/**************************************/
//SomeServiceImpl.java
package xyz.guawaz.ba01;
public class SomeServiceImpl implements SomeService {
@Override
public void doSome(String name, Integer age) {
System.out.println("=========目标方法doSome()=========");
}
}
4.创建切面类
//MyAspect.java
package xyz.guawaz.ba01;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import java.util.Date;
/**
* @Aspect: 是aspectj框架中的注解
* 作用:表示当前类是切面类
*/
@Aspect
public class MyAspect {
/**在切面类里面定义通知方法
* 要求:
* 1、方法时public的
* 2、方法没有返回值
* 3、方法的名称自定义
* 4、方法可以有参数也可以没有参数
* 如果有参数不能是自定义的,可以是某几个参数类型( 。。。 )
*
*/
/**
* @Before 前置通知注解
* 属性:value:切入点表达式,表示方法的执行位置
* 位置:通知方法的上面
* 特点:1.在目标方法之前先执行的
* 2.不会改变目标方法的执行结果
* 3.不会影响目标方法的执行
*
*/
/* @Before("execution(public void xyz.guawaz.ba01.SomeServiceImpl.doSome(String,Integer))")
public void MyBefore(){
System.out.println("前置通知,切面功能,在目标方法之前输出执行时间"+new Date());
}*/
//上面说通知方法可以有某几种参数,一种是JoinPoint类型的参数
/*
* 指定通知方法中的参数:JoinPoint
* JoinPonit:业务方法,即要加入切面功能的方法,即切入点
* 作用是:可以在通知方法执行时获取业务方法的信息,如方法名称、方法的实参、
*
* 如果通知方法中要用到业务方法的信息就可以加入JoinPoint
* 这个参数的值是由框架赋值的,要求必须放在第一个参数的位置
*
* */
@Before("execution(public void xyz.guawaz.ba01.SomeServiceImpl.doSome(String,Integer))")
public void MyBefore(JoinPoint joinPoint){
joinPoint.getSignature();
joinPoint.getArgs();
System.out.println("前置通知,切面功能,在目标方法之前输出执行时间"+new Date());
}
}
5.创建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">
<!--把对象交给spring容器,由spring统一创建和管理对象-->
<!--声明目标对象-->
<bean id="someService" class="xyz.guawaz.ba01.SomeServiceImpl"/>
<!--声明切面对象-->
<bean id="myAspect" class="xyz.guawaz.ba01.MyAspect"/>
<!--声明自动代理生成器:使用AspectJ框架内部的功能,创建目标对象的代理对象。
创建代理对象是在内存中实现的,它修改目标对象的内存中的结构,创建为代理对象
所以目标对象就是被修改后的代理对象
aspectj-autoproxy会把容器中所有的目标对象一次性都生成代理对象
-->
<aop:aspectj-autoproxy/><!--看上面idea自动帮忙引入的约束文件和名称空间-->
</beans>
6.创建测试类
package xyz.guawaz;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import xyz.guawaz.ba01.SomeService;
public class MyTest01 {
@Test
public void test1(){
ApplicationContext ac = new ClassPathXmlApplicationContext("ApplicationContext.xml");
/*从容器中获取目标对象*/
SomeService proxy = (SomeService) ac.getBean("someService");
/*通过代理对象执行方法*/
proxy.doSome("guawaz",19);
}
}
6.AspectJ的其他通知
后置通知@AfterReturning
package xyz.guawaz.ba02;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class MyAspect {
/**后置通知方法
* 要求:
* 1、方法是public的
* 2、方法没有返回值
* 3、方法的名称自定义
* 4、方法有参数 推荐是Object,参数名自定义
*/
/**
* @AfterReturning :后置通知
* 属性:1.value 切入点表达式
* 2.returning 自定义变量名必须和通知方法的形参名一样
* 位置:方法定义的上面
*
* 特点:1.目标方法指定之后执行
* 2.可以获取到目标方法的返回值 可以根据这个返回值做不同的处理
* Object res = doOther();
* 3.可以修改这个返回值
*
*/
@AfterReturning(value= "execution( * *..SomeServiceImpl.doOther(..))",returning = "res")
public void MyAfterReturning(JoinPoint joinPoint,Object res){
joinPoint.getSignature();
joinPoint.getArgs();
//Object res是目标方法执行后的返回值,可以根据返回值做切面的功能处理
System.out.println("后置通知,在目标方法执行之后执行,目标方法的返回值是"+res);
if (res != null) {
res = "cba";
}
}
@AfterReturning(value= "execution( * *..SomeServiceImpl.doOther1(..))",returning = "res1")
public void MyAfterReturning1(JoinPoint joinPoint,Object res1){
joinPoint.getSignature();
joinPoint.getArgs();
//Object res是目标方法执行后的返回值,可以根据返回值做切面的功能处理
System.out.println("后置通知,在目标方法执行之后执行,目标方法的返回值是"+res1);
Student res11 = (Student)res1;
res11.setAge(19);
}
}
环绕通知@Around
package xyz.guawaz.ba03;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import java.util.Date;
@Aspect
public class MyAspect {
/**环绕通知方法
* 要求:
* 1、方法是public的
* 2、方法可以有返回值,返回值就是目标方法的返回值,可以被修改
* 3、方法的名称自定义
* 4、方法有参数 ProceedingJoinPoint,它是JoinPoint的子接口,可以用来执行目标方法
* 5、方法可以抛出异常
*/
/**
* @Around :环绕通知
* 属性:1.value 切入点表达式
*
* 位置:方法定义的上面
*
* 特点:1.它是功能最强的通知
* 2.在目标方法的前后都能增强功能
* 3.可以控制目标方法是否执行
* 4.可以修改原来目标方法的执行结果
*
* 环绕通知等同于jdk动态代理的InvocationHandler接口
*
*
* 环绕通知用来处理事务,目标方法之前开启事务,执行目标方法,目标方法之后提交事务
*
*
*/
@Around(value= "execution( * *..SomeServiceImpl.doOther(..))")
public Object MyAround(ProceedingJoinPoint proceedingJoinPoint)throws Throwable{
System.out.println("环绕通知,在目标方法之前执行,输出时间" + new Date());
String name = "";
Object args[] = proceedingJoinPoint.getArgs();
System.out.println(args[0]);
if (args != null && args.length>1){
Object arg1 = args[0];
name = (String)arg1;
}
Object result = null;
if ("guawaz".equals(name)){
result = proceedingJoinPoint.proceed(); //根据条件控制目标方法是否执行
}
System.out.println("环绕通知,在目标方法执行之后执行,提交事务");
if (result != null){
result = "hello @Around!";
}
return result;
}
}
@AfterThrowing 异常通知
package xyz.guawaz.ba04;import org.aspectj.lang.annotation.AfterThrowing;import org.aspectj.lang.annotation.Aspect;@Aspectpublic class MyAspect { /**异常通知方法 * 要求: * 1、方法是public的 * 2、方法没有返回值 * 3、方法的名称自定义 * 4、方法有参数 Exception 还可以有JoinPoint */ /** * @ArfterThrowing:异常通知 * 属性:1.value 切入点表达式 * 2.throwing:自定义的变量,表示目标方法抛出的对象,变量名必须和方法的参数名一样 * * 位置:方法定义的上面 * * 特点:1.目标方法抛出异常时执行 * * 可以做异常的监控程序,如果目标方法有异常可以发送短信邮件进行通知 * * * * try{ * SomeSerciveImpl.doSecond(); * }catch(Exception e){ * myAfterThrowing(e); * } * * */ @AfterThrowing(value= "execution( * *..SomeServiceImpl.doSecond(..))",throwing = "ex") public void MyAround(Exception ex){ System.out.println("异常通知,在目标方法发生异常时执行,异常信息:" + ex.getMessage()); }}
最终通知@After
package xyz.guawaz.ba05;import org.aspectj.lang.annotation.After;import org.aspectj.lang.annotation.Aspect;@Aspectpublic class MyAspect { /**异常通知方法 * 要求: * 1、方法是public的 * 2、方法没有返回值 * 3、方法的名称自定义 * 4、方法没有参数,可以有JoinPoint */ /** * @Arfter:最终通知 * 属性:1.value 切入点表达式 * 位置:方法定义的上面 * * 特点:1.总是会执行,有异常也会执行 * 2.在目标方法之后执行 * * 用作资源关闭等功能 * */ @After(value= "execution( * *..SomeServiceImpl.doThird(..))") public void MyAfter(){ System.out.println("最终通知,在目标方法执行完毕之后执行" ); }}
@PointCut切入点注解
package xyz.guawaz.ba06;import org.aspectj.lang.annotation.After;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;@Aspectpublic class MyAspect { /** * @PointCut:切入点注解 * 属性:value 切入点表达式 * 位置:在一个自定义的方法定义的上面 * * 特点:当使用@PointCut定义在一个方法的上面,此时这个方法的名称就是这个切入点表达式的别名 * 其他的通知中就可以使用这个别名来代替切入点表达式了 */ @Pointcut(value= "execution( * *..SomeServiceImpl.doThird(..))") private void myPointCut(){} @Before(value="myPointCut()") public void Before(){ System.out.println("前置通知,在目标方法执行完毕之前执行" ); } @After(value= "myPointCut()") public void MyAfter(){ System.out.println("最终通知,在目标方法执行完毕之后执行" ); }}
补充
- 如果目标类有接口,spring默认使用jdk的方式创建代理对象
- 如果目标类没有接口,spring使用cglib创建代理对象
- 如果目标类有接口,也可以配置spring使用cglib创建代理对象,配置如下:
<aop:aspectj-autoproxy proxy-target-class="true"/>