前一篇讲解了Spring-IOC的简单应用可参考地址如下
https://blog.csdn.net/yi524529990/article/details/119453616?spm=1001.2014.3001.5501
本章主要介绍Spring AOP,AOP也叫面向切面编程,是一种编程思想,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术;
AOP采用横向抽取的机制,补充了继承无法解决代码重复性的问题 ,比如日志、事务管理、安全检查等等。
一.启动Spring容器
- java工程
ApplicationContext ctx = new ClasspathXmlApplicationContext("spring.xml");
- web工程
<!-- 基于xml -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>
<listener>
<listener-class>
ContextLoaderListener
</listener-class>
</listener>
<!-- 基于注解 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>注解配置类</param-value>
</context-param>
<listener>
<listener-class>
ContextLoaderListener
</listener-class>
</listener>
二、AOP的相关术语
1.连接点(Joinpoint)
连接点其实就是指拦截的点,spring中实际就是具体的方法
2.切入点(Pointcut)
指对哪些连接点进行拦截的定义
3.通知/增强(Advice)
指拦截到连接点后,接下来要做的事情,通知分为前置、后置、异常、环绕、最终通知
4.目标对象(Target)
代理的目标对象
5.切面(Aspect)
是切入点和通知的结合,也就是是一个类,自己来编写和配置
6.代理(Proxy)
一个类被AOP织入后会产生一个代理类
7.织入(Weaving)
通知/增强应用到目标对象创建新的代理对象的过程,一般分为
- 动态织入
动态织入的方式是在运行时动态将要增强的代码织入到目标类中,通过动态代理技术完成的,如 Java JDK 的动态代理和 CGLIB 的动态代理,Spring AOP 采用的就是基于运行时增强的代理技术
- 静态织入
ApectJ 采用的就是静态织入的方式,采用的是编译期织入,期间使用 AspectJ 的 acj 编译器(类似 javac)把 aspect 类编译成 class 字节码后,在 java 目标类编译时织入,即先编译 aspect 类再编译目标类;不过Spring 基于 AspectJ 的 AOP ,依然是采取的动态织入方式,spring将AspectJ整合到了自己的框架中
三、Spring 基于 AspectJ 的 AOP应用
1.依赖包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
2.编写目标对象(接口和实现类)
@Service
public class MyService {
public void myAdd(){
System.out.println("我要开始做事情了");
}
}
3.xml形式实现织入
/**通知类**/
public class MyAdvice {
public void mylog(){
System.out.println("记录日志信息");
}
}
<?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:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
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/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.test.beans" />
<!-- 配置通知 -->
<bean id = "myAdvice" class="com.test.beans.MyAdvice"></bean>
<!-- aop配置 -->
<aop:config>
<!-- 配置切面 -->
<aop:aspect ref="myAdvice">
<!-- method 通知/增强方法 -->
<!-- pointcut 切入点 -->
<aop:before method="mylog" pointcut="execution(void com.test.beans.MyService.myAdd())"/>
</aop:aspect>
</aop:config>
</beans>
/**测试类**/
public class TestMain2 {
public static void main(String[] args) {
// 1.获得Spring容器对象
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-context.xml");
MyService service = (MyService) context.getBean("myService");
service.myAdd();
}
}
4.注解形式实现织入
//一定要在配置类添加【EnableAspectJAutoProxy】
@EnableAspectJAutoProxy
@ComponentScan(value={"com.test.beans"})
@Configuration//相当于配置文件
public class ApplicationConfig {
}
@Aspect//表示一个切面类通知类
@Component
public class MyAdvice {
//定义切点
@Pointcut("execution(void com.test.beans.MyService.myAdd())")
public void pointCut(){
}
//定义前置通知
@Before("pointCut()")
public void beforemethod(){
System.out.println("记录日志信息");
}
//定义后置通知
@AfterReturning("pointCut()")
public void afterreturnemethod(){
System.out.println("记录日志信息");
}
//定义最终通知
@After("pointCut()")
public void afteremethod(){
System.out.println("记录日志信息");
}
//定义环绕通知
@Around("pointCut()")
public void aroundmethod(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("记录日志信息==========");
joinPoint.proceed(joinPoint.getArgs());
System.out.println("记录日志信息==========");
}
//定义异常通知
@AfterThrowing("pointCut()")
public void afterthrowingemethod(){
System.out.println("出现异常啦");
}
}
/**测试类**/
public class TestMain3 {
public static void main(String[] args) {
// 1.获得Spring容器对象
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(ApplicationConfig.class);
MyService service = (MyService) context.getBean("myService");
service.myAdd();
context.close();
}
}
5.切入点execution语法
- 表达式:execution([修饰符] 返回值类型 包名.类名.方法名(参数))
- 修饰符:可省略
- 返回值类型:必须要,但是可以使用*通配符
- 包名 :多级包之间使用.分割 ,包名可以使用*代替,多级包名可以使用多个*代替,如果想省略中间的包名可以使用 ..
- 类名:可以使用*代替 ,也可以写成*Impl形式
- 方法名: 可以使用*代替 ,也可以写成 add*
- 参数: 参数使用*代替 ,多个参数,可以使用 ..代替
例:executiion(* *..*.*DaoImpl.*(..))
表示任何返回类型.任何多级包名.DaoImpl结尾的类.任何方法名(多个参数)
6.通知类型
- 前置通知:对象方法之前执行<aop:before>
- 后置通知:对象方法之后执行,有异常则不执行了 <aop:after-returning>
- 最终通知:目标对象方法之后执行通知,有没有异常都会执行<aop:after> 相当于finally
- 环绕通知:目标对象方法之前和之后都会执行<aop:around>
- 异常抛出通知:在抛出异常后通知<aop:after-throwing>