目录
一、增加功能导致的问题
问题引出:如果要在已经实现的某个复杂的业务方法前,后增加新的功能,传统方法是怎么做的?
在源代码中业务方法中增加新功能,
1)源代码可能改动的比较多
2)重负代码多
3)代码难于维护
二、AOP的概念
2.1 什么是AOP
AOP(Aspect Orient Programming) : 面向切面编程
Aspect : 表示切面,给业务方法增加的功能叫做切面。切面一般都是非业务功能,而且切面功能一般都是可复用的。例如:日志功能,事务功能,权限检查,参数检查,统计信息等。
Orient : 面向,对看
Programming : 编程
怎么理解面向切面编程? 以切面为核心设计开发你的应用
1)设计项目时,找出切面的功能。
2)安排切面的执行时间,执行的位置。
2.2 AOP作用
1)让切面功能复用
2)让开发人员专注业务逻辑,提高开发效率
3)实现业务功能和其他非业务功能解耦合
4)给存在的业务方法增加功能,不用修改原来的代码
2.3 AOP中术语
1)Aspect : 切面,给业务方法增加的功能。
2)JoinPoint : 连接点,连接切面的业务方法。在这个业务方法执行时,会同时执行切面的功能。
3)Pointcut : 切入点,是一个或多个连接点的集合。表示这些方法执行时,都能增加切面的功能。表示切面执行 的位置。
4)target : 目标对象,给哪个对象增加切面的功能,这个对象就是目标对象。
5)Advice : 通知,表示切面的执行时间,在目标方法之前执行切面还是目标方法之后执行切面。
AOP中重要三要素:Aspect,Pointcut,Advice。这个概念的理解是:在Advice的时间,在Pointcut的位置,执行Aspect。
AOP是一个动态的思想。在程序运行期间,创建代理(ServiceProxy),使用代理执行方法时增加切面的功能。这个代理对象是存在内存中的。
三、什么时候用AOP
你要给某些方法增加相同的一些功能。源代码不能修改。给业务方法增加非业务功能。都可以使用AOP。
四、AOP技术思想的实现
使用框架实现AOP,实现AOP的框架有很多,比较有名的两个:
1)Spring : Spring框架实现AOP的部分功能。Spring框架实现AOP的操作比较繁琐,笨重。
2)Aspectj : 独立的框架,专门是实现AOP。属于Eclipse
五、使用AspectJ框架实现AOP
AspectJ框架可以使用注解和xml配置文件两种方式实现AOP
5.1通知
Aspectj表示切面执行的时间,用的是通知(Advice)。这个通知可以使用注解表示
5个注解,表示切面的五个执行时间,这些注解称为通知注解。
@Before : 前置通知
@AfterReturning : 后置通知
@Around : 环绕通知
@AfterThrowing : 异常通知
@After : 最终通知
5.2 Pointcut位置
Pointcut用来表示切面执行的位置,使用Aspectj中切入点表达式。
切入点表达式语法: execution(访问权限修饰符 方法返回值 方法声明(参数) 异常类型)
5.3 @before前置通知
使用aspectj框架的注解,实现前置通知:
①业务逻辑
package com.feiyang.service;
public interface SomeService {
void doSome(String name,Integer age);
}
package com.feiyang.service.impl;
import com.feiyang.service.SomeService;
/**
* @author:飞扬
* @公众hao:程序员飞扬
* @description:
*/
public class SomeServiceImpl implements SomeService {
@Override
public void doSome(String name, Integer age) {
System.out.println("业务方法doSome(),执行了");
}
}
②定义一个切面类:
package com.feiyang.handler;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import java.util.Date;
/**
* @author:飞扬
* @公众hao:程序员飞扬
* @description:
*/
@Aspect
public class MyAspectj {
@Before(value = "execution(public void com.feiyang.service.impl.SomeServiceImpl.doSome(String, Integer))")
public void myBefore(){
System.out.println("前置通知执行了" + new Date());
}
}
③修改配置文件
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="Index of /schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd Index of /schema/context https://www.springframework.org/schema/context/spring-context.xsd Index of /schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="someService" class="com.feiyang.service.impl.SomeServiceImpl"></bean>
<bean id="myAspect" class="com.feiyang.handler.MyAspectj"></bean>
<!-- 声明自动代理生成器:目的是创建目标对象的代理 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
④测试类:
package com.feiyang;
import com.feiyang.service.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author:飞扬
* @公众hao:程序员飞扬
* @description:
*/
public class Mytest01 {
@Test
public void test01(){
String config = "applicationContext.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
SomeService someService = (SomeService) ctx.getBean("someService");
System.out.println(someService.getClass().getName());
someService.doSome("张三",18);
}
}
执行结果:
com.sun.proxy.$Proxy8
前置通知执行了Tue Oct 04 11:18:25 GMT+08:00 2022
业务方法doSome(),执行了
可以看到someService运行时类型为代理对象$Proxy8,
代理对象调用方法,才有切面的功能增强
切入点表达式的变化形式
package com.feiyang.handler;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import java.util.Date;
/**
* @author:飞扬
* @公众hao:程序员飞扬
* @description:
*/
@Aspect
public class MyAspectj {
//匹配指定方法
/*@Before(value = "execution(public void com.feiyang.service.impl.SomeServiceImpl.doSome(String, Integer))")
public void myBefore(){
System.out.println("前置通知执行了" + new Date());
}*/
//匹配任意访问修饰符的指定方法
/*@Before(value = "execution(void com.feiyang.service.impl.SomeServiceImpl.doSome(String, Integer))")
public void myBefore(){
System.out.println("前置通知执行了" + new Date());
}*/
//匹配任意包下的,指定参数类型的doSome方法
/*@Before(value = "execution(* *..doSome(String, Integer))")
public void myBefore(){
System.out.println("前置通知执行了" + new Date());
}*/
//匹配任意包下,任意参数类型的doSome方法
/*@Before(value = "execution(* *..doSome(..))")
public void myBefore(){
System.out.println("前置通知执行了" + new Date());
}*/
//匹配任意包下,do开头的方法
/*@Before(value = "execution(* *..do*(..))")
public void myBefore(){
System.out.println("前置通知执行了" + new Date());
}*/
//匹配任意包下任意方法
@Before(value = "execution(* *..*(..))")
public void myBefore(){
System.out.println("前置通知执行了" + new Date());
}
}
通知方法的参数JoinPoint
package com.feiyang.handler;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import java.util.Date;
/**
* @author:飞扬
* @公众hao:程序员飞扬
* @description:
*/
@Aspect
public class MyAspectj {
@Before(value = "execution(* *..*(..))")
public void myBefore(JoinPoint jp){
Object[] args = jp.getArgs();//数组中存放的是 方法的所有参数
for (Object arg : args) {
System.out.println("前置通知,获取方法的参数: " + arg);
}
String methodName = jp.getSignature().getName();
if("doSome".equals(methodName)){
System.out.println("doSome输出日志,在目标方法前先执行=" + new Date());
}else if("doOther".equals(methodName)){
System.out.println("doOther参数检查,在目标方法前先执行=" + new Date());
}else{
System.out.println("没有匹配到通知方法");
}
}
}
package com.feiyang;
import com.feiyang.service.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author:飞扬
* @公众hao:程序员飞扬
* @description:
*/
public class Mytest01 {
@Test
public void test01(){
String config = "applicationContext.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
SomeService someService = (SomeService) ctx.getBean("someService");
someService.doSome("张三",18);
//someService.doOther("张三",18);
}
}
执行结果:
前置通知,获取方法的参数: 张三
前置通知,获取方法的参数: 18
doSome输出日志,在目标方法前先执行=Tue Oct 04 13:25:54 GMT+08:00 2022
业务方法doSome(),执行了
5.4 @AfterReturning后置通知
@AfterReturning后置通知
属性:value 切入点表达式
returning 自定义的变量,表示目标方法的返回值
自定义变量名称必须和通知方法的形参名一样
位置:在方法的上面
特点:
1.在目标方法之后执行的。
2.能获取到目标方法的执行结果
3.不会影响目标方法的执行
方法的参数:
Object res:表示目标方法的返回值,使用res接收doAfter()的调用结果,
Object res = doAfter()。
后置通知的执行顺序:
Object res = SomeServiceImpl.doAfter();
myAfter(res);
demo:
package com.feiyang.service;
public interface SomeService {
String doAfter();
}
package com.feiyang.service.impl;
import com.feiyang.service.SomeService;
/**
* @author:飞扬
* @公众hao:程序员飞扬
* @description:
*/
public class SomeServiceImpl implements SomeService {
@Override
public String doAfter() {
System.out.println("后置通知doAfter()方法执行了");
return "res";
}
}
package com.feiyang.handler;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import java.util.Date;
/**
* @author:飞扬
* @公众hao:程序员飞扬
* @description:
*/
@Aspect
public class MyAspectj {
//后置通知
@AfterReturning(value = "execution(* *..doAfter(..))",returning = "res")
public void myAfter(Object res){
System.out.println("后置通知,在目标方法之后执行,能拿到业务执行结果:" + res);
}
}
package com.feiyang;
import com.feiyang.service.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author:飞扬
* @公众hao:程序员飞扬
* @description:
*/
public class Mytest01 {
@Test
public void test01(){
String config = "applicationContext.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
SomeService someService = (SomeService) ctx.getBean("someService");
someService.doAfter();
}
}
执行结果:
后置通知doAfter()方法执行了
后置通知,在目标方法之后执行,能拿到业务执行结果:res
注意:JoinPoint jp 必须是第一个参数
//后置通知
@AfterReturning(value = "execution(* *..doAfter(..))",returning = "res")
public void myAfter(JoinPoint jp,Object res){
System.out.println("后置通知,在目标方法之后执行,能拿到业务执行结果:" + res);
}
下面这样会报错:
//后置通知
@AfterReturning(value = "execution(* *..doAfter(..))",returning = "res")
public void myAfter(Object res,JoinPoint jp){
System.out.println("后置通知,在目标方法之后执行,能拿到业务执行结果:" + res);
}
5.5 @Around环绕通知
@Around : 环绕通知
属性 : value
位置 : 在方法定义的上面
返回值 : Object,表示调用目标方法希望得到执行结果(不一定是目标方法自己的返回值)
参数 : ProceedingJoinPoint,相当于反射中的Method
作用 : 执行目标方法的,等于Method.invoke()
特点 :
1.在目标方法前后都能增强功能
2.控制目标方法是否执行
3.修改目标方法的执行结果
demo:
package com.feiyang.service;
public interface SomeService {
String doFirst(String name);
}
package com.feiyang.service.impl;
import com.feiyang.service.SomeService;
/**
* @author:飞扬
* @公众hao:程序员飞扬
* @description:
*/
public class SomeServiceImpl implements SomeService {
@Override
public String doFirst(String name) {
System.out.println("业务方法执行了。。。");
return "sss";
}
}
package com.feiyang.handler;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import java.util.Date;
/**
* @author:飞扬
* @公众hao:程序员飞扬
* @description:
*/
@Aspect
public class MyAspectj {
//后置通知
@AfterReturning(value = "execution(* *..doAfter(..))",returning = "res")
public void myAfter(JoinPoint jp,Object res){
System.out.println("后置通知,在目标方法之后执行,能拿到业务执行结果:" + res);
}
//环绕通知
@Around("execution(* *..doFirst(..))")
public void myAround(ProceedingJoinPoint pjp) throws Throwable {
//可以控制在业务方法执行前执行什么逻辑
System.out.println("执行了环绕通知的myAround()方法,在业务方法执行前");
//可以控制业务方法是否执行
String returnValue = (String)pjp.proceed();
//可以控制返回值
String res="";
if("aaa".equals(returnValue)){
res = returnValue;
}else{
res = "bbb";
}
//可以控制在业务方法执行后执行什么逻辑
System.out.println("执行了环绕通知的myAround()方法,在业务方法执行后: " + res);
}
}
package com.feiyang;
import com.feiyang.service.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author:飞扬
* @公众hao:程序员飞扬
* @description:
*/
public class Mytest01 {
//环绕通知
@Test
public void test03(){
String config = "applicationContext.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
SomeService someService = (SomeService) ctx.getBean("someService");
someService.doFirst("张三");
}
}
执行结果:
执行了环绕通知的myAround()方法,在业务方法执行前
业务方法执行了。。。
执行了环绕通知的myAround()方法,在业务方法执行后bbb
5.6 @AfterThrowing异常通知
//异常通知
@AfterThrowing(value = "execution(* *..doSecond(..))", throwing = "ex")
public void myAfterThrowing(Exception ex){
System.out.println("异常通知,执行目标方法时抛异常了,异常原因:" + ex.getMessage());
}
@Override
public void doSecond(String name) {
int z;
z = 5/0;
System.out.println("异常通知业务方法执行了。");
}
//异常通知
@Test
public void test04(){
String config = "applicationContext.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
SomeService someService = (SomeService) ctx.getBean("someService");
someService.doSecond("lisi");
}
执行结果:
异常通知,执行目标方法时抛异常了,异常原因:/ by zero
5.7 @After最终通知
在目标方法之后执行,总是会被执行,可以用来做程序最后的首位工作,例如清除临时数据,变量,清理内存等。
//最终通知
@After("execution(* *..doThird(..))")
public void myAfter(){
System.out.println("最终通知总是会执行。");
}
//最终通知
@Test
public void test05(){
String config = "applicationContext.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
SomeService someService = (SomeService) ctx.getBean("someService");
someService.doThird();
}
执行结果:
业务方法执行了。
最终通知总是会执行。
5.8 @Pointcut
定义和管理切入点,不是通知注解。
package com.feiyang.handler;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import java.util.Date;
/**
* @author:飞扬
* @公众hao:程序员飞扬
* @description:
*/
@Aspect
public class MyAspectj {
@Before(value = "mypt()")
public void method1(){
System.out.println("业务逻辑执行了");
}
@After(value = "mypt()")
public void method2(){
System.out.println("业务逻辑执行了");
}
@Pointcut("execution(* *..doThird(..))")
public void mypt(){
//无需代码
}
}
六、AOP总结
AOP是一种动态的技术思想,目的是实现业务功能和非业务功能的解耦合。业务功能是独立的模块,其他功能也是独立的业务模块。例如事务功能,日志等等。让这些功能,如事务,日志等是可以复用的。
当目标方法需要一些功能时,可以在不修改源代码的情况下,使用aop的技术在程序执行期间,生成代理对象,通过执行业务方法,增加了功能。