继续学习,方便自己复查记录
①AOP简介:
面向切面编程(也叫面向方面编程):Aspect Oriented Programming(AOP)。
Spring框架中的一个重要内容。。
通过预编译方式和运行期间动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。AOP是OOP(面向对象编程)的延续。
作用: 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高 程序的可重用性,同时提高了开发的效率。
使用场景:日志记录,性能统计,安全控制,事务处理,异常处理等。
②AOP实例学习:几种通知类型↓↓↓↓
1,前置通知;
2,后置通知;
3,环绕通知;
4,返回通知;
5,异常通知;
②-1首先看下不用AOP前的代码写法,
(作为测试,业务目的很简单:在程序的开始和结束打印一点日志)
代码如下:涉及4个文件
代码1:配置类 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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="studentService" class="com.java1234.service.impl.StudentServiceImpl"></bean>
</beans>
代码2:StudentService类
package com.java1234.service;
public interface StudentService {
public void addStudent(String name);
}
代码3:StudentService实现类
package com.java1234.service.impl;
import com.java1234.service.StudentService;
public class StudentServiceImpl implements StudentService{
@Override
public void addStudent(String name) {
System.out.println("开始添加学生"+name);
System.out.println("添加学生"+name);
System.out.println("完成学生"+name+"的添加");
}
}
代码4:测试类
package com.java1234.test;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.java1234.service.StudentService;
public class T {
private ApplicationContext ac;
@Before
public void setUp() throws Exception {
ac=new ClassPathXmlApplicationContext("beans.xml");
}
@Test
public void test1() {
// 多态特性:父类的引用可以指向具体的实现。
// 按照bean.xml的定义,本来 ac.getBean("studentService") 得到的是StudentServiceImpl,被安全的向下转型为StudentService类型
StudentService studentService=(StudentService)ac.getBean("studentService");//
studentService.addStudent("张三");
}
}
②-2使用AOP类的变化:
a.增加切面通知类
b.引入相关jar包:aopalliance.jar,aspectjweaver-1.6.6.jar,spring-aspects-4.0.6.RELEASE.jar(版本号可能有差异)
c.在bean.xml中增加
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation 后面增加http://www.springframework.org/schema/aop
加完上面这个就可以在bean.xml中使用aop功能了
++插入知识点1:bean.xml的命名空间的含义。可以跳过++
xsi:schemaLocation 的通用结构为: 命名空间URI1 XSD路径1 命名空间URI2 XSD路径2 ...
例子:(都是两个两个一起的)
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
在给定的 xsi:schemaLocation 属性中,每一对字符串分别由命名空间 URI 和 实际 XSD 文件路径组成。具体解析如下:
命名空间 URI
http://www.springframework.org/schema/beans
这是 Spring 框架中 beans 模块的命名空间 URI,用于标识 XML 中相关元素的所属规范。
http://www.springframework.org/schema/aop
这是 Spring AOP 模块的命名空间 URI,用于标识 AOP 相关配置的命名空间。
实际 XSD 文件路径
http://www.springframework.org/schema/beans/spring-beans.xsd
这是 beans 模块对应的 XML Schema 定义文件(XSD)的实际网络路径。
http://www.springframework.org/schema/aop/spring-aop.xsd
这是 AOP 模块对应的 XSD 文件路径,定义了 AOP 配置的校验规则。
插入知识点2:执行表达式execution
的理解,可以跳过
执行表达式
execution(* com.java1234.service.*.*(..))
的含义该表达式是 Spring AOP 中的切点(Pointcut)表达式,用于匹配特定方法的执行。以下是逐部分解析:
组成部分解析
execution
表示匹配方法执行的连接点(Join Point),是 AOP 中最常用的切点指示符。
*
(第一个星号)匹配任意返回类型(如
void
、String
、int
等)。
com.java1234.service.*
指定包路径和类名:
com.java1234.service
是目标包名。.*
匹配该包下的任意类(但不包括子包中的类)。
*
(第三个星号)匹配类中的任意方法名。
(..)
匹配任意参数列表(无论是否有参数,或参数类型是什么)。
完整含义
该表达式匹配:
com.java1234.service
包下任意类中,所有返回类型、所有方法名、所有参数列表的方法。常见用法示例
@Aspect @Component public class ServiceAspect { @Before("execution(* com.java1234.service.*.*(..))") public void logBeforeServiceMethod(JoinPoint joinPoint) { System.out.println("Executing: " + joinPoint.getSignature()); } }
扩展说明
- 若需匹配子包中的类,可使用
..
:(2个点)
execution(* com.java1234.service..*.*(..))
。- 若需限定方法名,可替换
*
为具体名称(如get*
匹配以get
开头的方法)。相关语法对比
// 匹配特定返回类型(String)的方法 execution(String com.java1234.service.*.*(..)) // 匹配特定类(UserService)中的方法 execution(* com.java1234.service.UserService.*(..)) // 匹配特定参数类型(需一个Long参数)的方法 execution(* com.java1234.service.*.*(Long))
d.在bean.xml中增加aop切面的定义 ,完整的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: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">
<!-- 引入第三行 xmlns:aop="http://www.springframework.org/schema/aop" 和
引入第7.8行 http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
就可以使用AOP功能配置了 -->
<!-- 切面类是studentServiceAspect(这里有想添加的方法功能) -->
<bean id="studentServiceAspect" class="com.java1234.advice.StudentServiceAspect"></bean>
<bean id="studentService" class="com.java1234.service.impl.StudentServiceImpl"></bean>
<aop:config>
<!--aop:aspect 配置切面,关联的切面类是studentServiceAspect(这里有想添加的方法功能) -->
<aop:aspect id="studentServiceAspectConfig" ref="studentServiceAspect">
<!-- aop:pointcut 定义1个切点,spring的切点是定义到方法级的。execution是表达式,用于匹配特定方法的执行-->
<!-- execution(* com.java1234.service.*.*(..)) 用于匹配特定方法的执行 -->
<aop:pointcut expression="execution(* com.java1234.service.*.*(..))" id="businessService"/>
<!-- 开始定义各种通知 aop:before aop:after aop:around aop:after-returning aop:after-returning aop:after-throwing-->
<!-- 定义1,前置通知;方法执行前触发. pointcut-ref="businessService" 表示引用的切点是businessService-->
<aop:before method="doBefore" pointcut-ref="businessService" />
<!-- 定义2,后置通知;方法执行后触发(无论是否抛出异常)。 -->
<!--aop:after method="doAfter" pointcut-ref="businessService"/-->
<!-- 定义3,环绕通知;包围目标方法,需手动调用 proceed()。 -->
<aop:around method="doAround" pointcut-ref="businessService" />
<!-- 定义4,返回通知; 方法正常返回后触发。-->
<aop:after-returning method="doAfterReturning" pointcut-ref="businessService"/>
<!-- 定义5,异常通知 方法抛出异常时触发。-->
<aop:after-throwing method="doAfterThrowing" pointcut-ref="businessService" throwing="ex"/>
</aop:aspect>
</aop:config>
</beans>
其他的代码1,业务类:没有了冗余的日志打印代码,用aop实现了
package com.java1234.service.impl;
import com.java1234.service.StudentService;
public class StudentServiceImpl implements StudentService{
@Override
public void addStudent(String name) {
// System.out.println("开始添加学生"+name);
System.out.println("业务逻辑开始::::添加学生"+name);
// System.out.println(1/0);
// System.out.println("完成学生"+name+"的添加");
}
}
代码2: 接口类(匹配上expression="execution(* com.java1234.service.*.*(..))"这里的路径)
package com.java1234.service;
public interface StudentService {
public void addStudent(String name);
}
代码3:切面类(5种通知的具体内容)
package com.java1234.advice;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class StudentServiceAspect {
public void doBefore(JoinPoint jp){
System.out.println("doBefore:类名:"+jp.getTarget().getClass().getName());
System.out.println("doBefore:方法名:"+jp.getSignature().getName());
System.out.println("doBefore:开始添加学生:"+jp.getArgs()[0]);
}
public void doAfter(JoinPoint jp){
System.out.println("doAfter:类名:"+jp.getTarget().getClass().getName());
System.out.println("doAfter:方法名:"+jp.getSignature().getName());
System.out.println("doAfter:学生添加完成:"+jp.getArgs()[0]);
}
public Object doAround(ProceedingJoinPoint pjp) throws Throwable{ // 参数和其他通知不一样 ProceedingJoinPoint
System.out.println("doAround:添加学生前");
System.out.println("doAround:手动调用 proceed方法");
Object retVal=pjp.proceed(); // 需手动调用 proceed()。
//System.out.println(retVal);
System.out.println("doAround:添加学生后");
return retVal;
}
public void doAfterReturning(JoinPoint jp){
System.out.println("doAfterReturning:返回通知");
}
public void doAfterThrowing(JoinPoint jp,Throwable ex){
System.out.println("doAfterThrowing:异常通知");
System.out.println("doAfterThrowing:异常信息:"+ex.getMessage());
}
}
代码4:测试类,调用的main类
package com.java1234.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.java1234.service.StudentService;
public class T {
public static void main(String[] args) {
ApplicationContext ac=new ClassPathXmlApplicationContext("beans.xml");
StudentService studentService=(StudentService)ac.getBean("studentService");
studentService.addStudent("张三");
}
}
最后:关于5种通知:
1,前置通知;
2,后置通知;
3,环绕通知;
4,返回通知;
5,异常通知;
ps1: 第4种返回通知;第5种异常通知; 不会同时存在,只能同时返回1种
ps2. 如果同时存在时的执行顺序:
在业务处理无异常时的执行顺序:
1前置通知--》3环绕通知的前处理--》业务逻辑--》3环绕通知的后处理--》2后置通知--》4返回通知
在业务处理有异常时的执行顺序:
1前置通知--》3环绕通知的前处理--》业务逻辑--》5异常通知