Spring03--AOP 面向切面编程
1.1 AOP 简介
AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程序运行过程。
AOP 底层,就是采用动态代理模式实现的。采用了两种代理:JDK 的动态代理,与 CGLIB的动态代理。
面向切面编程,就是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指通用的、与主业务逻辑无关的代码,如安全检查、事务、日志、缓存等。若不使用 AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样,会使主业务逻辑变的混杂不清。
1.2 面向切面编程的好处
1.减少重复;
2.专注业务;
1.3 AOP 编程术语
切面(Aspect)
连接点(JoinPoint)
切入点(Pointcut)
目标对象(Target)
通知(Advice)
1.4 AspectJ 对 AOP 的实现
对于 AOP 这种编程思想,很多框架都进行了实现。Spring 就是其中之一,可以完成面向切面编程。然而,AspectJ 也实现了 AOP 的功能,且其实现方式更为简捷,使用更为方便,而且还支持注解式开发。所以,Spring 又将 AspectJ 的对于 AOP 的实现也引入到了自己的框架中。
AspectJ 简介
1.4.1 AspectJ 的通知类型
AspectJ 中常用的通知有五种类型:
(1)前置通知
(2)后置通知
(3)环绕通知
(4)异常通知
(5)最终通知
1.4.2 AspectJ 的切入点表达式
AspectJ 定义了专门的表达式用于指定切入点。表达式的原型是:
execution(modifiers-pattern?
ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)
以上表达式共 4 个部分。
execution(访问权限 方法返回值 方法声明(参数) 异常类型)
切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就是方法的签名。注意,表达式中黑色不加粗文字表示可省略部分,各部分间用空格分开。在其中可以使用以下符号:
符号 | 意义 |
---|---|
* | 0至多个任意字符 |
… | 用在参数方法中,表示任意多个参数;用在包名后,表示当前包及其子包路径。 |
+ | 用在类名后,表示当前类及其子类;用在接口后,表示当前接口及其实现类。 |
1.4.3 AspectJ 的开发环境
1)maven依赖
<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>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
2)引入 AOP 约束
在 AspectJ 实现 AOP 时,要引入 AOP 的约束。配置文件中使用的 AOP 约束中的标签,均是 AspectJ 框架使用的,而非 Spring 框架本身在实现 AOP 时使用的。
AspectJ 对于 AOP 的实现有注解和配置文件两种方式,常用是注解方式。
1.4.4 AspectJ 基于注解的 AOP 实现
1)前置通知
测试接口:
public interface SomeService {
void doSome(String name,Integer age);
}
接口实现类:
public class SomeServiceImpl implements SomeService {
@Override
public void doSome(String name,Integer age) {
//给doSome方法增加一个功能,在doSome()执行之前, 输出方法的执行时间
System.out.println("====目标方法doSome()====");
}
}
切面类:
/**
* @Aspect : 是aspectj框架中的注解。
* 作用:表示当前类是切面类。
* 切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
* 位置:在类定义的上面
*/
@Aspect
public class MyAspect {
/**
* @Before: 前置通知注解
* 属性:value ,是切入点表达式,表示切面的功能执行的位置。
* 位置:在方法的上面
* 特点:
* 1.在目标方法之前先执行的
* 2.不会改变目标方法的执行结果
* 3.不会影响目标方法的执行。
*/
@Before(value = "execution(public void com.suyv.spring.ba01.SomeServiceImpl.doSome(String,Integer))")
public void myBefore(){
//就是你切面要执行的功能代码
System.out.println("前置通知, 切面功能:在目标方法之前输出执行时间:"+ new Date());
}
spring配置文件:
<!--声明目标对象-->
<bean id="someService" class="com.suyv.spring.ba01.SomeServiceImpl" />
<!--声明切面类对象-->
<bean id="myAspect" class="com.suyv.spring.ba01.MyAspect" />
<!--声明自动代理生成器:使用aspectj框架内部的功能,创建目标对象的代理对象。
创建代理对象是在内存中实现的, 修改目标对象的内存中的结构。
创建为代理对象所以目标对象就是被修改后的代理对象.
aspectj-autoproxy:会把spring容器中的所有的目标对象,一次性都生成代理对象。
-->
<aop:aspectj-autoproxy />
测试方法:
@Test
public void test01(){
String config="applicationContext.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
//从容器中获取目标对象
SomeService proxy = (SomeService) ctx.getBean("someService");
System.out.println("proxy:"+proxy.getClass().getName());
//通过代理的对象执行方法,实现目标方法执行时,增强了功能
proxy.doSome("lisi",20);
}
测试结果:
2)后置通知
测试接口:
public interface SomeService {
void doSome(String name, Integer age);
String doOther(String name,Integer age);
}
接口实现类:
public class SomeServiceImpl implements SomeService {
@Override
public void doSome(String name,Integer age) {
//给doSome方法增加一个功能,在doSome()执行之前, 输出方法的执行时间
System.out.println("====目标方法doSome()====");
}
@Override
public String doOther(String name, Integer age) {
System.out.println("====目标方法doOther()====");
return "abcd";
}
}
切面类:
@Aspect
public class MyAspect {
/**
* @AfterReturning:后置通知
* 属性:1.value 切入点表达式
* 2.returning 自定义的变量,表示目标方法的返回值的。
* 自定义变量名必须和通知方法的形参名一样。
* 位置:在方法定义的上面
* 特点:
* 1。在目标方法之后执行的。
* 2. 能够获取到目标方法的返回值,可以根据这个返回值做不同的处理功能
* Object res = doOther();
* 3. 可以修改这个返回值
*/
@AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",
returning = "res")
public void myAfterReturing( JoinPoint jp ,Object res ){
// Object res:是目标方法执行后的返回值,根据返回值做你的切面的功能处理
System.out.println("后置通知:方法的定义"+ jp.getSignature());
System.out.println("后置通知:在目标方法之后执行的,获取的返回值是:"+res);
if(res.equals("abcd")){
//做一些功能
} else{
//做其它功能
}
//修改目标方法的返回值, 看一下是否会影响 最后的方法调用结果
if( res != null){
res = "Hello Aspectj";
}
}
}
spring配置文件:
<!--声明目标对象-->
<bean id="someService" class="com.suyv.spring.ba02.SomeServiceImpl" />
<!--声明切面类对象-->
<bean id="myAspect" class="com.suyv.spring.ba02.MyAspect" />
<!--声明自动代理生成器-->
<aop:aspectj-autoproxy />
测试方法:
@Test
public void test01(){
String config="applicationContext.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
//从容器中获取目标对象
SomeService proxy = (SomeService) ctx.getBean("someService");
//通过代理的对象执行方法,实现目标方法执行时,增强了功能
String str = proxy.doOther("zs",28);
System.out.println("str===="+str);
}
测试结果:
3)环绕通知
测试接口:
public interface SomeService {
void doSome(String name, Integer age);
String doOther(String name, Integer age);
String doFirst(String name,Integer age);
}
接口实现类:
public class SomeServiceImpl implements SomeService{
@Override
public void doSome(String name,Integer age) {
//给doSome方法增加一个功能,在doSome()执行之前, 输出方法的执行时间
System.out.println("====目标方法doSome()====");
}
@Override
public String doOther(String name, Integer age) {
System.out.println("====目标方法doOther()====");
return "abcd";
}
@Override
public String doFirst(String name, Integer age) {
System.out.println("====业务方法doFirst()====");
return "doFirst";
}
}
切面类:
@Aspect
public class MyAspect {
/**
* @Around: 环绕通知
* 属性:value 切入点表达式
* 位置:在方法的定义什么
* 特点:
* 1.它是功能最强的通知
* 2.在目标方法的前和后都能增强功能。
* 3.控制目标方法是否被调用执行
* 4.修改原来的目标方法的执行结果。 影响最后的调用结果
*/
@Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
String name = "";
//获取第一个参数值
Object args [] = pjp.getArgs();
if( args!= null && args.length > 1){
Object arg= args[0];
name =(String)arg;
}
//实现环绕通知
Object result = null;
System.out.println("环绕通知:在目标方法之前,输出时间:"+ new Date());
//1.目标方法调用
if( "zhangsan".equals(name)){
//符合条件,调用目标方法
result = pjp.proceed(); //method.invoke(); Object result = doFirst();
}
System.out.println("环绕通知:在目标方法之后,提交事务");
//2.在目标方法的前或者后加入功能
//修改目标方法的执行结果, 影响方法最后的调用结果
if( result != null){
result = "Hello AspectJ AOP";
}
//返回目标方法的执行结果
return result;
}
}
spring配置文件:
<!--声明目标对象-->
<bean id="someService" class="com.suyv.spring.ba03.SomeServiceImpl" />
<!--声明切面类对象-->
<bean id="myAspect" class="com.suyv.spring.ba03.MyAspect" />
<!--声明自动代理生成器-->
<aop:aspectj-autoproxy />
测试方法:
@Test
public void test01(){
String config="applicationContext.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
//从容器中获取目标对象
SomeService proxy = (SomeService) ctx.getBean("someService");
//通过代理的对象执行方法,实现目标方法执行时,增强了功能
String str = proxy.doFirst("zhangsan",20); /// myAround()
}
测试结果:
4)异常通知
测试接口:
public interface SomeService {
void doSome(String name, Integer age);
String doOther(String name, Integer age);
String doFirst(String name, Integer age);
void doSecond();
}
接口实现类:
//目标类
public class SomeServiceImpl implements SomeService {
@Override
public void doSome(String name,Integer age) {
//给doSome方法增加一个功能,在doSome()执行之前, 输出方法的执行时间
System.out.println("====目标方法doSome()====");
}
@Override
public String doOther(String name, Integer age) {
System.out.println("====目标方法doOther()====");
return "abcd";
}
@Override
public String doFirst(String name, Integer age) {
System.out.println("====业务方法doFirst()====");
return "doFirst";
}
@Override
public void doSecond() {
System.out.println("执行业务方法doSecond()" + (10/0));
}
}
切面类:
@Aspect
public class MyAspect {
/**
* @AfterThrowing:异常通知
* 属性:1. value 切入点表达式
* 2. throwinng 自定义的变量,表示目标方法抛出的异常对象。
* 变量名必须和方法的参数名一样
* 特点:
* 1. 在目标方法抛出异常时执行的
* 2. 可以做异常的监控程序, 监控目标方法执行时是不是有异常。
* 如果有异常,可以发送邮件,短信进行通知
*/
@AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",
throwing = "ex")
public void myAfterThrowing(Exception ex) {
System.out.println("异常通知:方法发生异常时,执行:"+ex.getMessage());
}
}
spring配置文件:
<!--声明目标对象-->
<bean id="someService" class="com.suyv.spring.ba04.SomeServiceImpl" />
<!--声明切面类对象-->
<bean id="myAspect" class="com.suyv.spring.ba04.MyAspect" />
<!--声明自动代理生成器-->
<aop:aspectj-autoproxy />
测试方法:
@Test
public void test01(){
String config="applicationContext.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
//从容器中获取目标对象
SomeService proxy = (SomeService) ctx.getBean("someService");
//通过代理的对象执行方法,实现目标方法执行时,增强了功能
proxy.doSecond();
}
测试结果:
5)最终通知
测试接口:
public interface SomeService {
void doSome(String name, Integer age);
String doOther(String name, Integer age);
String doFirst(String name, Integer age);
void doSecond();
void doThird();
}
接口实现类:
public class SomeServiceImpl implements SomeService {
@Override
public void doSome(String name,Integer age) {
//给doSome方法增加一个功能,在doSome()执行之前, 输出方法的执行时间
System.out.println("====目标方法doSome()====");
}
@Override
public String doOther(String name, Integer age) {
System.out.println("====目标方法doOther()====");
return "abcd";
}
@Override
public String doFirst(String name, Integer age) {
System.out.println("====业务方法doFirst()====");
return "doFirst";
}
@Override
public void doSecond() {
System.out.println("执行业务方法doSecond()" + (10/0));
}
@Override
public void doThird() {
System.out.println("执行业务方法doThird()"+ (10/0));
}
}
切面类:
@Aspect
public class MyAspect {
/**
* @After :最终通知
* 属性: value 切入点表达式
* 位置: 在方法的上面
* 特点:
* 1.总是会执行
* 2.在目标方法之后执行的
*/
@After(value = "execution(* *..SomeServiceImpl.doThird(..))")
public void myAfter(){
System.out.println("执行最终通知,总是会被执行的代码");
}
}
spring配置文件:
<!--声明目标对象-->
<bean id="someService" class="com.suyv.spring.ba05.SomeServiceImpl" />
<!--声明切面类对象-->
<bean id="myAspect" class="com.suyv.spring.ba05.MyAspect" />
<!--声明自动代理生成器-->
<aop:aspectj-autoproxy />
测试方法:
@Test
public void test01(){
String config="applicationContext.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
//从容器中获取目标对象
SomeService proxy = (SomeService) ctx.getBean("someService");
//通过代理的对象执行方法,实现目标方法执行时,增强了功能
proxy.doThird();
}
测试结果:
1.5 CGLIB对AOP的实现
测试接口:
public interface SomeService {
void doSome(String name,Integer age);
}
接口实现类:
public class SomeServiceImpl implements SomeService {
@Override
public void doSome(String name,Integer age) {
//给doSome方法增加一个功能,在doSome()执行之前, 输出方法的执行时间
System.out.println("====目标方法doSome()====");
}
}
切面类:
@Aspect
public class MyAspect {
@Before(value = "execution(public void com.suyv.spring.ba01.SomeServiceImpl.doSome(String,Integer))")
public void myBefore(){
//就是你切面要执行的功能代码
System.out.println("前置通知, 切面功能:在目标方法之前输出执行时间:"+ new Date());
}
spring配置文件:
<!--声明目标对象-->
<bean id="someService" class="com.suyv.spring.ba01.SomeServiceImpl" />
<!--声明切面类对象-->
<bean id="myAspect" class="com.suyv.spring.ba01.MyAspect" />
<!--
如果你期望目标类有接口,使用cglib代理
proxy-target-class="true":告诉框架,要使用cglib动态代理
-->
<aop:aspectj-autoproxy proxy-target-class="true"/>
测试方法:
@Test
public void test01(){
String config="applicationContext.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
//从容器中获取目标对象
SomeService proxy = (SomeService) ctx.getBean("someService");
System.out.println("proxy:"+proxy.getClass().getName());
//通过代理的对象执行方法,实现目标方法执行时,增强了功能
proxy.doSome("lisi",20);
}
测试结果: