AOP使用步骤:
1)、导包
commons-logging-1.2.jar
spring-aop-4.0.0.RELEASE.jar
spring-beans-5.0.4.RELEASE.jar
spring-context-5.0.4.RELEASE.jar
spring-core-5.0.4.RELEASE.jar
spring-expression-5.0.4.RELEASE.jar
spring支持面向切面编程的包:
spring-aspects-4.0.0.RELEASE.jar 基础版
加强版的面向对象编程 (即使目标对象没有实现任何接口也能创建动态代理):
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
2)、写配置
1、将目标类和切面类(封装了通知方法(在目标方法执行前后执行的方法))加入到IOC容器中
2、还应该告诉spring到底哪个是切面类 用@Aspect
3、告诉spring,切面类里面的每一个方法,都是合适何地运行;
```java
*告诉spring每个方法都什么时候运行
* * @Before:在执行目标方法之前运行 前置通知
* * @After:在执行目标方法结束的时候运行 后置通知
* * @AfterReturning:在目标方法正常执行后运行 返回通知
* * @AfterThrowing:在目标方法抛出异常后运行 异常通知
* * @Around :环绕 环绕通知
* * */
* //想在执行目标方法之前运行 ;写切入点表达式
* //execution(方法签名)
* @Before("execution(public int com.ssm.impl.MyMathCalculator.*(int, int))")
* public static void logStart( ){
* System.out.println("这是动态代理将要帮你执行方法。。。。");
* System.out.println("[xxx]方法开始执行了用的参数[xxx]");
* }
* //想在执行目标方法之后运行
* @AfterReturning("execution(public int com.ssm.impl.MyMathCalculator.*(int, int))")
* public static void logEnd() {
* System.out.println("[xxx]方法执行完成 结果是:[xxx]");
* }
* //想在目标方法出现异常运行
* @AfterThrowing("execution(public int com.ssm.impl.MyMathCalculator.*(int, int))")
* public static void logException() {
* System.out.println("[xxx]方法执行出现异常 异常信息是:[xxx]");
* }
* //想在目标方法正常结束后运行
* @After("execution(public int com.ssm.impl.MyMathCalculator.*(int, int))")
* public static void logfinally() {
* System.out.println("[xxx]方法最终结束了");
* }
``
4、开启基于注解的AOP模式
3)、测试
1、细节一:IOC容器中保存的是组件的代理对象;
@Test
public void test01() {
//从ioc中拿到目标对象; 注意:如果想要用类型,一定用她的接口类型,不要用它本类
/*细节一:com.ssm.impl.MyMathCalculator@4961f6af
* class com.sun.proxy.$Proxy17
* * Aop底层就是动态代理 容器中保存的组件就是他的代理对象 :$Proxy17。当然不是本类类型
* */
* /*Calculator bean = ioc.getBean(Calculator.class);
* //bean.add(8, 850);
* // System.out.println(Calculator.class);
* //System.out.println(bean);
* System.out.println(bean.getClass());
* */
* //获取代理对象还可以用id 注解扫描创建的bean的id默认值是类名首字母小写。
* /* Calculator bean1 = (Calculator) ioc.getBean("myMathCalculator");
* System.out.println(bean1.getClass());
* */
* /*
* // 1)、有接口就是非本类类型
* Calculator bean = ioc.getBean(MyMathCalculator.class);
* 注意:MyMathCalculator 是本类 获取他时容器会为他创建代理对象 即$Proxy17 容器中存在的是它的代理对象 并没有本类 ;
* */
* // 2)、没有接口就是本类类型
* //通过Id获取对象
* MyMathCalculator bean = (MyMathCalculator) ioc.getBean("myMathCalculator");
* bean.add(2, 5);
* //通过BySpringCGLIB创建的一个代理对象
* System.out.println(bean.getClass());
* //通过类型获取对象
* MyMathCalculator bean1 = ioc.getBean(MyMathCalculator.class);
* bean.add(2, 5);
* System.out.println(bean1.getClass());
* }
2、细节二:切入点表达式写法(通配符);
切入点表达式的写法:
固定格式:execution(访问权限符 返回值类型 方法全类名(参数表))
通配符:"":
1)、匹配一个或多个字符 execution(public int com.ssm.impl.MyMath.(int, int))
2)、匹配任意一个参数
3)、只能匹配一层路径
4)、权限位置不能写 不写即为任意权限;
@Before("execution(public int com.ssm.impl.MyMathCalculator.*(int, *))")
通配符: …:
1)、匹配任意多个参数,任意类型参数
2)、匹配任意多层路径
@Before("execution(public int com..MyMathCalculator.*(int, *))")//com下任意层路径下的MyMathCalculator
3、细节三:通知方法的执行顺序
try{
@Before
mothod.invoke(obj,args);
@AfterReturning
}catch{
@AfterThrowing
}finally{
@After
}
正常执行:@Before(前置通知)=@After(后置通知)=@AfterReturning(正常返回)
异常执行:@Before(前置通知)=@After(后置通知)=@AfterThrowing(异常返回)
4、细节四、五:我们可以在通知方法运行的时候,拿到目标方法的详细信息,
1)、只需要为通知方法的参数列表写一个参数:
JoinPoint joinPoint :封装了当前目标方法的详细信息
2)、告诉spring这个throwing是用来接受返回值的
throwing=“exception”
告诉spring这个result是用来接受返回值的
returning=“result”
3)、Exception exception:指定通知方法可以接收哪些异常;
Object result:指定通知方法可以接收哪些结果;
5、细节六:spring对通知方法的约束,(参数表一定正确)
spring对通知方法不严格:以下两种情况都能正常运行
唯一要求就是方法的参数列表不能乱写:
通知方法是spring利用反射调用的,每次方法调用逗得确定这个方法的参数表的值;
参数表上的每个参数,spring都得知道;
不知道的参数一定要告诉spring
public int logEnd(JoinPoint joinPoint ) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println("["+name+"]方法最终结束了");
return 0;
}
private int logEnd(JoinPoint joinPoint ) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println("["+name+"]方法最终结束了");
return 0;
}
6、细节七:抽取可重用的切入点表达式;
1、随声明一个没有实现的返回void的空方法
2、给方法上标注@pointcut注解
@After("hahaMyPoint()")
public static void logEnd(JoinPoint joinPoint ) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
ystem.out.println("["+name+"]方法最终结束了");
}
@Pointcut("execution(public int com.ssm.impl.MyMathCalculator.*(int, int))")
public void hahaMyPoint(){ }
7、细节八:环绕通知;
@Around :环绕通知是spring中最强大的通知
实际就是手写的动态代理
try{
前置通知
mothod.invoke(obj,args); //目标方法执不执行环绕通知说的算
返回通知
}catch{
异常通知
}finally{
后置通知 }
四合一就是环绕通知;
环绕通知中有一个参数:ProceedingJoinPoint pjp
@Around("hahaMyPoint()")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable{
Signature signature = pjp.getSignature();
String name = signature.getName();
Object[] args = pjp.getArgs();
Object proceed=null;
try {
System.out.println("环绕前置通知["+name+"]方法开始执行了用的参数["+Arrays.asList(args)+"]");
//就是利用反射调用目标方法即可 就是method.invoke(obj,args)
proceed = pjp.proceed(args);
System.out.println("环绕返回通知["+name+"]方法执行完成 结果是:["+proceed+"]");
} catch (Exception e) {
System.out.println("环绕异常通知["+name+"]方法执行出现异常 异常信息是:["+e+"]");
}finally{
System.out.println("环绕后置通知["+name+"]方法最终结束了");
}
//反射调用后的返回值也一要返回出去;
return proceed;
}
8、细节九:环绕通知的执行顺序;
环绕通知优先于普通通知执行
执行顺序:
普通前置
{
try{
环绕前置
环绕执行:目标方法执行
环绕正常返回
}catch{
环绕出现异常
}finally{
环绕后置
}
}
普通后置 普通返回/异常
新的顺序: (环绕前置普通前置)目标方法执行环绕正常返回/出现异常环绕后置普通后置普通返回/异常
(环绕前置==普通前置) 这俩顺序随机无所谓 一旦目标方法执行完毕 需严格按照环绕在前 普通在后
9、细节十:多切面的运行顺序;
1)、字母顺序会决定顺序;
2)、 @order(num): num越大优先级越高
切面图:没有@Around 时执行顺序
有@Around 时执行顺序
来源:CSDN
作者:福尔康刘
链接:https://blog.csdn.net/m0_46501053/article/details/104717997