在源代码中,业务方法中增加的功能。
1)源代码可能改动的比较多。
2)重复代码比较多。
3)代码难于维护。
AOP(Aspect Orient Programming): 面向切面编程
Aspect : 表示切面,给业务方法增加的功能,叫做切面。切面一般都是非业务功能,而且切面功能一般都是可以复用的。例如日志功能,事务功能 ,权限检查 ,参数检查 ,统计信息等等。
Orient :面向,对着。
Programming : 编程。
以切面为核心设计开发的应用。
1) 设计项目时,找出切面的功能。
2) 安排切面的执行时间,执行的位置。
1)让切面功能复用。
2)让开发人员专注业务逻辑。提高开发的效率。
3)实现业务功能和其他非业务功能解耦合。
4)给存在的业务方法增加功能,不用修改原来的代码。
1)Aspect:切面,给业务方法增加的功能。
2)JoinPoint:连接点,连接切面的业务方法。在这个业务方法执行时,会同时执行切面的功能。
3)Pointcut:切入点,是一个或多个连接点集合。表示这些方法执行时,都能增加切面的功能。
表示切面执行的位置。
4)target:目标对象,给那个对象增加切面的功能,这个对象就是目标对象。
5)Advice: 通知(增强),表示切面的执行时间。在目标方法之前执行切面,还是目标方法之后执行切面。
这个概念的理解是:在Advice的时间,在Pointcut的位置,执行Aspect.
AOP是一个动态的思想。在程序运行期间,创建代理(ServiceProxy),使用代理执行方法时,增加切面的功能。这个代理对象是存在内存中的。
你要给某些方法增加相同的一些功能。源代码不能改。给业务方法增加非业务功能,也可以使用AOP。
使用框架实现AOP。实现AOP的框架有很多。有名的两个
1)Spring: Spring框架实现AOP思想中的部分功能。Spring框架实现AOP的操作比较繁琐,笨重。
2)Aspectj: 独立的框架,专门是AOP。属于Eclipse
Aspectj框架可以使用注解和xml配置文件两种方式实现AOP。
Aspectj表示切面执行时间,用的通知(Advice)。这个通知可以使用注解表示。讲5个注解,表示切面的5个执行时间,这些注解叫做通知注解。
@Before:前置通知。
@AfterReturning: 后置通知。
@Around : 环绕通知。
@AfterThrowing : 异常通知。
@After : 最终通知。
Pointcut 用来表示切面执行的位置,使用Aspectj中切入点表达式。
切入点表达式语法 : execution(方法的定义)
execution( 访问权限 方法返回值 方法声明(参数) 异常类型 )
ch07-aspectj-before:使用aspectj-before:使用aspectj框架的注解,实现前置通知
实现步骤:
1.新建maven项目
2.修改pom.xml 加入依赖
spring-context依赖,spring-aspects依赖(能使用aspectj框架的功能)
junit
3.创建业务接口和实现类。
4.创建一个叫做切面类,是一个普通类
1)在类的上面加入@Aspect
2)在类中定义方法,方法表示切面的功能。
在方法的上面加入Aspect框架中的通知注解,例如@Before(value="切入点表达式")
5.创建spring配置文件。
1)声明目标对象
2)声明切面类对象
3)声明自动代理生成器
6.创建测试类,测试目标方法执行时,增加切面的功能。
applicationContext.xml
声明自动代理生成器:目的是创建目标对象的代理(就是06项目中的ServiceProxy)
调用aspectj框架中的功能,寻找Spring容器中所有的目标对象,
把每一个目标对象加入切面类中的功能,生成代理。
这个代理对象是修改的内存中的目标对象,这个目标就是代理对象(ServiceProxy)
-->
<aop:aspectj-autoproxy/>
切面类
* @Aspect: 切面类的注解。
* 位置:放在某个类的上面
* 作用:表示当前类是切面类。
*
* 切面类:表示切面功能的类。
* */
@Aspect
public class MyAspect {
// 定义方法,表示切面的具体功能
* 前置通知方法的定义
* 1)方法是public
* 2) 方法是void
* 3)方法名称自定义
* 4)方法可以有参数,如果有JoinPoint
* 也可以没有。
* */
* @Before:前置通知
* 属性:value切入点表达式,表示切面的执行位置。
* 在这个方法时,会同时执行切面的功能。
* 位置:在方法的上面
*
* 特点:
* 1)执行时间:在目标方法之前先执行。
* 2)不会影响目标方法的执行。
* 3)不会修改目标方法的执行结果。
* */
// @Before(value = "execution(public void com.sunny.service.impl.SomeServiceImpl.doSome(String, Integer))")
// @Before(value = "execution(void com.sunny.service.impl.SomeServiceImpl.doSome(String, Integer))")
// @Before(value = "execution(* *..doSome(..))")
/**
* 切面类中的通知方法,可以有参数
* JoinPoint必须是他。
*
* JoinPoint:表示正在执行的业务方法。 相当于反射中的Method
* 使用要求:必须是参数列表的第一个。
* 作用:获取方法执行时的信息,例如方法名称,参数集合。
* */
@Before(value = "execution(* *..do*(..))")
public void myBefore2(JoinPoint jp){
// 获取方法的定义
System.out.println("前置通知中,获取目标方法的定义:"+jp.getSignature());
System.out.println("前置通知中,获取方法名称=="+jp.getSignature().getName());
// 获取方法执行时参数
Object[] args = jp.getArgs();
// 数组中存放的是方法的所有参数
for (Object obj:args){
System.out.println("前置通知,获取方法的参数:"+obj);
}
String methodName = jp.getSignature().getName();
if("doSome".equals(methodName)){
//切面代码
System.out.println("doSome输出日志:前置通知,切面的功能,在目标方法之前先执行:"+new Date());
}else if ("doOther".equals(methodName)){
System.out.println("doOther前置通知,作为方法名称,参数的记录。");
}
}
}
public class MyTest {
@Test
public void test01(){
String config = "applicationContext.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
SomeService service = (SomeService) ctx.getBean("someService");
service.doThird();
}
}
@AfterReturning: 在目标方法之后执行的
package com.sunny.handle;
@Aspect
public class MyAspect {
// 定义方法,表示切面的具体功能
* 后置通知方法的定义
* 1)方法是public
* 2) 方法是void
* 3)方法名称自定义
* 4)方法有参数,推荐使用Object类型
* */
* @AfterReturning:后置通知
* 属性:value 切入点表达式
* returning 自定义的变量,表示目标方法的返回值的。
* 自定义变量名称必须和通知方法的形参名一样。
* 位置:在方法的上面
*
* 特点:
* 1.在目标方法之后执行的。
* 2.能获取到目标方法的执行结果。
* 3.不会影响目标方法的执行
*
* 方法的参数:
* Object res: 表示方法的返回值,使用res接收doOther的调用结果。
* Object res = doOther();
*
* 后置通知的执行顺序
* Object res = SomeServiceImpl.doOther(..);
* myAfterReturning(res);
*
* 思考:
* 1.doOther方法返回的是String,Integer,Long等基本类型。
* 在后置通知中,返回修改值,是不会影响目标方法的最后调用的结果的。
* 2.doOther返回的结果是对象类型,例如Student.
* 在后置通知方法中,修改这个Student对象的属性值,会不会影响最后调用结构?
* */
// JoinPoint一定是第一个参数
@AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",returning = "res")
public void myAfterReturning(JoinPoint jp,Object res){
// 修改目标方法的返回值。
if(res!= null){
res = "Hello Aspectj";
}
System.out.println("后置通知,在目标方法之后执行的。能拿到执行结果:"+res);
//Object res有什么用
if("abcd".equals(res)){
System.out.println("根据返回值的不同,做不同的增强功能");
}else if("add".equals(res)){
System.out.println("doOther做了添加数据库,我做了备份数据");
}
}
}
@Around(value=“切入点表达式”
使用环绕通知就是调用切面类的通知方法。
@Aspect
public class MyAspect {
* 环绕置通知方法的定义
* 1)方法是public
* 2) 方法是是必须有返回值,推荐使用Object类型
* 3)方法名称自定义
* 4)方法必须有ProceedingJoinPoint参数
* */
* @Around : 环绕通知
* 属性 :value切入点表达式
* 位置 :在方法定义上面
*
* 返回值 :Object , 表示调用目标方法希望得到执行结果(不一定是目标方法自己的返回值)
* 参数 : ProceedingJoinPoint:相当于反射中 Method
* 作用:执行目标方法的,等于Method.invoke()
*
* public interface ProceedingJoinPoint extends JoinPoint
*
* 特点:
* 1.在目标方法的前和后都能增强功能
* 2.控制目标方法是否执行
* 3.修改目标方法的执行结果。
*
* */
@Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("执行了环绕通知的myAround方法");
// 获取方法执行时的参数值
String name = "";
Object args [] = pjp.getArgs();
if(args!= null&& args.length>0){
Object arg = args[0];
if(arg != null){
name = (String)arg;
}
}
Object methodReturn = null;
System.out.println("执行了环绕通知,在目标方法之前,输出日志时间=="+new Date());
// 执行目标方法 ProceedingJoinPoint ,表示doFirst
methodReturn = pjp.proceed();//method.invoke(),表示doFirst()方法本身
if("lisi".equals(name)){
methodReturn = pjp.proceed();
//,ethod.invoke(),表示执行doFirst()方法本身
}
if(methodReturn != null){
methodReturn = "环绕通知中,修改目标方法原来的执行结果";
}
System.out.println("环绕通知,在目标方法之后,增加了事务提交、功能");
// 返回目标方法执行结果。没有修改的。
return methodReturn;
}
}
语法 @AfterThrowing(value=“切入点表达式”,throwing=“自定义变量”)
@Aspect
public class MyAspect {
* 异常通知方法的定义
* 1)方法是public
* 2) 方法是是没有返回值,是void
* 3)方法名称自定义
* 4)方法有参数是Exception
* */
* @AfterThrowing :异常通知
* 属性:value 切入点表达式
* throwing 自定义变量,表示目标方法抛出的异常。
* 变量名必须和通知方法的形参名一样
* 位置:在方法的上面
*特点:
* 1.在目标方法抛出异常后执行的,没有异常不执行
* 2.能获取到目标方法的异常信息。
* 3.不是异常处理程序。可以得到发生异常的通知,可以发送邮件,短信通知开发人员。
* 看做是目标方法的监控程序。
*
* 异常通知的执行
* try{
* SomeServiceImpl.doSecond(..)
* }catch(Exception e){
* myAfterThrowing(e);
* }
* */
@AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",throwing = "ex")
public void myAfterThrowing(Exception ex){
System.out.println("异常通知,在目标方法抛出异常时执行的,异常原因是:"+ex.getMessage());
/**
* 异常发生时可以做:
* 1.记录异常的时间,位置等信息。
* 2.发送邮件,短信,通知开发人员
*
* */
}
}
语法:@After(value=“切入点表达式”)
package com.sunny.handle;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import java.util.Date;
@Aspect
public class MyAspect {
/**
* 异常通知方法的定义
* 1)方法是public
* 2) 方法是是没有返回值,是void
* 3)方法名称自定义
* 4)方法没有参数
* */
/**
* @After: 最终通知
* 属性:value 切入点表达式
* 位置:在方法的上面
* 特点:
* 1.在目标方法之后执行的。
* 2.总是会被执行。
* 3.可以用来做程序最后的收尾工作。例如清理临时数据,变量。清理内存
*
* 最终通知
* try{
* SomeServiceImpl.doThird(..)
* }finally{
* myAfter();
* }
* */
@After(value = "execution(* *..SomeServiceImpl.doThird(..))")
public void myAfter(){
System.out.println("最终通知,总是会被执行的");
}
}
@Pointcut(value=“切入点表达式”)
@Aspect
public class MyAspect {
@Before(value = "mypt()")
public void myBefore(){
System.out.println("前置通知,在目标方法之前执行的");
}
@After(value = "mypt()")
public void myAfter(){
System.out.println("最终通知,总是会被执行的");
}
/**
* @Pointcut: 定义和管理切入点,不是通知注解
* 属性:value 切入点表达式
* 位置:在一个自定义方法的上面,这个方法看做是切入点表达式的别名。
* 其他通知注解中,可以使用方法名称就表示使用这个切入点表达式了
*
* */
@Pointcut("execution(* *..SomeServiceImpl.doThird(..))")
public void mypt(){
// 无需代码
}
@Pointcut("execution(* *..SomeServiceImpl.doThird(..))")
private void mypt1(){
// 无需代码
}
}
AOP是一种动态的技术思想,目的是实现业务功能和非业务功能的解耦合。业务功能也是独立的模块。例如事务功能,日志等等。让这些事务,日志功能可以被复用。
当目标方法需要一些功能时,可以在不修改,不能修改源码的情况下,使用aop技术在程序执行期间,生成代理对象,通过代理执行业务方法,同时增加功能。
ch13-aop-homework:使用aop做方法的参数检查
要求:
1.当addNumber 方法的参数,是不为null的时候。
2.当addNumber 方法的参数是大于0时。才能执行addNumber()计算三个数的和。
3.如果任意一个参数是 null ,或者小于 0 。则调用addNumber返回结果是 -1
4.使用aop做addNumber()方法的参数检查。
package com.firewolf.handle;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
/**
* 1.当addNumber 方法的参数,是不为null的时候。
* 2.当addNumber 方法的参数是大于0时。才能执行addNumber()计算三个数的和。
* 3.如果任意一个参数是 null ,或者小于 0 。则调用addNumber返回结果是 -1
* 4.使用aop做addNumber()方法的参数检查。
* */
@Aspect
public class MyAspect {
boolean flag = false;
@Pointcut(value = "execution(* *..NumberServiceImpl.addnumber(..))")
private void pct(){
}
// Around方法先于Before执行,因此没有用before来检查参数
@Around(value = "pct()")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
Object object = null;
Object[] args = pjp.getArgs();
for(Object arg:args){
Integer in = (Integer) arg;
if(in==null||in < 0 ){
flag =true;
}
}
if(flag==false){
// 参数符合要求
System.out.println("参数符合结果");
object = pjp.proceed();
}else{
System.out.println("参数不符合结果");
object = -1;
}
return object;
}
}
//接口
package com.firewolf.service;
public interface NumberService {
Integer addnumber(Integer n1,Integer n2,Integer n3);
}
//测试类
@Test
public void test01(){
String config = "ApplicationContext.xml";
ApplicationContext context =new ClassPathXmlApplicationContext(config);
NumberService service = (NumberService) context.getBean("numberServiceImpl");
// service.addnumber(1,2,3);
System.out.println(service.addnumber(1,2,3));
System.out.println(service.addnumber(-1,1,1));
System.out.println(service.addnumber(null,1,1));
}
applicationContext.xml
<aop:aspectj-autoproxy/>
<bean id="myAspect" class="com.firewolf.handle.MyAspect"/>
<bean id="numberServiceImpl" class="com.firewolf.service.NumberServiceImpl"/>
PS:不能用before检查参数,因为before先于around执行。
只问耕耘,不问收获