继承机制
封装方法
动态代理
……
3.情景举例
①数学计算器接口[MathCalculator]
int add(int i,int j);
int sub(int i,int j);
int mul(int i, int j);
public interface MathCaculator { public int add(int i,int j); public int sub(int i,int j); public int mul(int i,int j); public int div(int i,int j); }
@Component public class CacultorEasyImpl implements MathCaculator{ @Override public void add(int i, int j) { System.out.println("[日志],【参数:】"+i+","+j); int result = i + j; System.out.println("[日志],【参数:】"+i+","+j+"--"+result); } @Override public void sub(int i, int j) { System.out.println("[日志],【参数:】"+i+","+j); int result = i - j; System.out.println("[日志],【参数:】"+i+","+j+"--"+result); } @Override public void mul(int i, int j) { System.out.println("[日志],【参数:】"+i+","+j); int result = i * j; System.out.println("[日志],【参数:】"+i+","+j+"--"+result); } @Override public void div(int i, int j) { System.out.println("[日志],【参数:】"+i+","+j); int result = i / j; System.out.println("[日志],【参数:】"+i+","+j+"--"+result); } }
<context:component-scan base-package="com.neuedu.aop"></context:component-scan>
③在简单实现的基础上让每一个计算方法都能够打印日志[LoginImpl]
private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml"); @Test public void test() { CacultorEasyImpl bean = ioc.getBean(CacultorEasyImpl.class); bean.add(10, 2); bean.sub(10, 2); bean.mul(10, 2); bean.div(10, 2); }
④缺陷
[1]手动添加日志繁琐,重复
[2]统一修改不便
[3]对目标方法本来要实现的核心功能有干扰,使程序代码很臃肿,不易于开发维护
⑤使用动态代理实现
[1]创建一个类,让这个类能够提供一个目标对象的代理对象
[2]在代理对象中打印日志
AOP概述
●AOP(Aspect-Oriented Programming,面向切面编程):是一种新的方法论
●Spring的AOP既可以使用xml配置的方式实现,也可以使用注解的方式来实现!
5.在Spring中使用AOP实现日志功能
①Spring中可以使用注解或XML文件配置的方式实现AOP。
②导入jar包
com.springsource.net.sf.cglib -2.2.0.jar
com.springsource.org.aopalliance-1.0.0 .jar
com.springsource.org.aspectj.weaver-1.6.8 .RELEASE.jar
commons-logging-1.1.3. jar
spring-aop-4.0.0.RELEASE.jar
spring-aspects-4.0.0.RELEASE.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE. jar
③开启基于注解的AOP功能 < aop:aspectj-autoproxy />
<context:component-scan base-package="com.neuedu.aop"></context:component-scan> <aop:aspectj-autoproxy/>
④声明一个切面类,并把这个切面类加入到 IOC容器中
在类上加以下两个注解
@Aspect :表示这是一个切面类
@Component :加入IOC容器
⑤在切面类中声明通知方法
[1] 前置通知:@Before
[2] 返回通知:@AfterReturning
[3] 异常通知:@AfterThrowing
[4] 后置通知:@After
[5] 环绕通知:@Around :环绕通知是前面四个通知的集合体!
@Component @Aspect public class CaculatorAspect { @Before(value="execution(public void com.neuedu.aop.RawCaculatorImpl.add(int, int))") public void showBeginLog(){ System.out.println("日志开始"); } @After(value="execution(public void com.neuedu.aop.RawCaculatorImpl.add(int, int))") public void showReturnLog(){ System.out.println("日志正常返回"); } @AfterThrowing(value="execution(public void com.neuedu.aop.RawCaculatorImpl.add(int, int))") public void showExceptionLog(){ System.out.println("日志有错"); } @AfterReturning(value="execution(public void com.neuedu.aop.RawCaculatorImpl.add(int, int))") public void showAfterLog(){ System.out.println("日志最终结束"); } }
⑥被代理的对象也需要加入IOC容器
@Component public class RawCaculatorImpl implements MathCaculator{ @Override public void add(int i, int j) { int result = i + j; System.out.println(i+"+"+j+"="+result); } @Override public void sub(int i, int j) { int result = i - j; System.out.println(i+"-"+j+"="+result); } @Override public void mul(int i, int j) { int result = i * j; System.out.println(i+"*"+j+"="+result); } @Override public void div(int i, int j) { int result = i / j; System.out.println(i+"/"+j+"="+result); } }
Test 中 用 id 查找,通过强转,调用加减乘除四个方法
private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml"); @Test public void test() { MathCaculator bean = (MathCaculator) ioc.getBean("rawCaculatorImpl"); bean.add(10, 2); bean.sub(10, 2); bean.mul(10, 2); bean.div(10, 2); }
@Component @Aspect public class CaculatorAspect { @Before(value="execution(public void com.neuedu.aop.RawCaculatorImpl.*(int, int))") public void showBeginLog(){ System.out.println("日志开始"); } @After(value="execution(public void com.neuedu.aop.RawCaculatorImpl.*(int, int))") public void showReturnLog(){ System.out.println("日志正常返回"); } @AfterThrowing(value="execution(public void com.neuedu.aop.RawCaculatorImpl.*(int, int))") public void showExceptionLog(){ System.out.println("日志有错"); } @AfterReturning(value="execution(public void com.neuedu.aop.RawCaculatorImpl.*(int, int))") public void showAfterLog(){ System.out.println("日志最终结束"); } }
如果方法中的参数类型不一致,可以用 (..) 代替 (int,int)
@Component @Aspect public class CaculatorAspect { @Pointcut(value="execution(* com.neuedu.aop.RawCaculatorImpl.*(..))") public void showLog(){} @Before(value="showLog()") public void showBeginLog(JoinPoint point){ Object[] args = point.getArgs();//获取参数 List<Object> asList = Arrays.asList(args);//转为list类型 Signature signature = point.getSignature();//获取签名 String name = signature.getName();//获取方法名字 System.out.println("【前置通知】目标方法名:"+name+",参数为:"+asList); } @After(value="showLog()") public void showReturnLog(){ System.out.println("【后置通知】日志最终返回"); } @AfterThrowing(value="showLog()",throwing="ex") public void showExceptionLog(JoinPoint point,Exception ex){ System.out.println("【异常通知】异常信息为:"+ex.getMessage()); } @AfterReturning(value="showLog()",returning="result") public void showAfterLog(JoinPoint point,Object result){ System.out.println("【返回通知】方法的返回值:"+result); System.out.println(); } }
参见第5章AOP细节:演示验证
1.任意参数,任意类型
2.任意返回值
3.用@PointCut注解统一声明,然后在其它通知中引用该统一声明即可!
需要注意的是:权限是不支持写通配符的,当然你可以写一个*表示所有权限所有返回值!
最详细的切入点表达式:
execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))
最模糊的切入点表达式:
@Pointcut(value= "execution(public int com.atguigu.aop.target.EazyImpl.add(int,int))")
public void myPointCut(){}
8.通知方法的细节
①在通知中获取目标方法的方法名和参数列表
[3]调用JoinPoint对象的getArgs()方法获取目标方法的实际参数列表
@Before(value="showLog()") public void showBeginLog(JoinPoint point){ Object[] args = point.getArgs();//获取参数 List<Object> asList = Arrays.asList(args);//转为list类型 Signature signature = point.getSignature();//获取签名 String name = signature.getName();//获取方法名字 System.out.println("目标方法名:"+name+",参数为:"+asList); System.out.println("日志开始"); }
@AfterReturning(value="showLog()",returning="result") public void showAfterLog(JoinPoint point,Object result){ System.out.println("方法的返回值:"+result); System.out.println("日志正常结束"); System.out.println(); }
@AfterThrowing(value="showLog()",throwing="ex") public void showExceptionLog(JoinPoint point,Exception ex){ System.out.println("异常信息为:"+ex.getMessage()); System.out.println("日志有错"); }
10.环绕通知:@Around
1.环绕通知需要在方法的参数中指定JoinPoint的子接口类型ProceedingJoinPoint为参数
@Around(value="pointCut()")
public void around(ProceedingJoinPoint joinPoint){
}
2.环绕通知会将其他4个通知能干的,自己都给干了!
@Around(value="execution(public * com.neuedu.aop.RawCaculatorImpl.*(..))") public Object showLog(ProceedingJoinPoint point){ Object[] args = point.getArgs(); List<Object> asList = Arrays.asList(args); Signature signature = point.getSignature();//获取签名 String name = signature.getName(); Object result=null; try { try{ System.out.println("【前置通知】目标方法名:"+name+",参数为:"+asList); result = point.proceed(args); }finally{ System.out.println("【后置通知】日志最终返回"); } System.out.println("【返回通知】方法的返回值:"+result); } catch (Throwable e) { System.out.println("【异常通知】异常信息为:"+e.getMessage()); } System.out.println(); return result; }
对于同一个代理对象,可以同时有多个切面共同对它进行代理。
@Component @Aspect @Order(value=20) public class BAspect { @Around(value="execution(* com.neuedu.aop.RawCaculatorImpl.*(..))") public Object showLog(ProceedingJoinPoint point){ Object[] args = point.getArgs(); List<Object> asList = Arrays.asList(args); Signature signature = point.getSignature();//获取签名 String name = signature.getName(); Object result=null; try { try{ System.out.println("【前置】目标方法名:"+name+",参数为:"+asList); result = point.proceed(args); }finally{ System.out.println("【后置】日志最终返回"); } System.out.println("【返回】方法的返回值:"+result); } catch (Throwable e) { System.out.println("【异常】异常信息为:"+e.getMessage()); } System.out.println(); return result; } }
<!-- 将需要加载到IOC容器中的bean配置好 --> <bean id="caculatorAspect" class="com.neuedu.aop.CaculatorAspect"></bean> <bean id="bAspect" class="com.neuedu.aop.BAspect"></bean> <bean id="rawCaculatorImpl" class="com.neuedu.aop.RawCaculatorImpl"></bean> <!-- 配置AOP,需要导入AOP名称空间 --> <aop:config> <!-- 声明切入点表达式 --> <aop:pointcut expression="execution(* com.neuedu.aop.RawCaculatorImpl.*(..))" id="myPointCut"/> <!-- 配置日志切面类,引用前面的类 ,通过order属性控制优先级--> <aop:aspect ref="caculatorAspect" order="20"> <!-- 通过method属性指定切面类的切面方法,通过pointcut-ref指定切入点表达式 --> <aop:before method="showBeginLog" pointcut-ref="myPointCut"/> <aop:after method="showAfterLog" pointcut-ref="myPointCut"/> <aop:after-returning method="showReturnLog" pointcut-ref="myPointCut" returning="result"/> <aop:after-throwing method="showExceptionLog" pointcut-ref="myPointCut" throwing="ex"/> </aop:aspect> <!-- 配置事务切面类,引用前面的类 --> <aop:aspect ref="bAspect" order="25"> <aop:around method="showLog" pointcut-ref="myPointCut"/> </aop:aspect> </aop:config>
需要知道的是:事务的管理是和AOP是有很大关系的,即声明式事务的底层是用AOP实现的!