1.概念
1.1 什么是AOP
AOP是面向切面编程,能够让我们在不影响原有功能的前提下,为软件横向扩展功能 ,"横向"是指"持久层" "业务层" 控制器层"的任意一层,实现AOP的技术主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“切面”,从而使得编译器可以在编译期间织入有关“切面”的代码。
1.2 什么是横切关注点
在AOP中,我们将这些具有公共逻辑的,与其他模块的核心逻辑纠缠在一起的行为称为“横切关注点(Crosscutting Concern)”,因为它跨越了给定编程模型中的典型职责界限。例如:一个信用卡处理系统的核心关注点是借贷/存入处理,而系统级的关注点则是日志、事务完整性、授权、安全及性能问题等,即横切关注点(crosscutting concerns)。
1.3 动态横切
动态横切是通过切入点和连接点在一个方面中创建行为的过程,连接点可以在执行时横向地应用于现有对象。动态横切通常用于帮助向对象层次中的各种方法添加日志记录或身份认证。
1.4 静态横切
静态横切和动态横切的区别在于它不修改一个给定对象的执行行为。相反,它允许通过引入附加的方法字段和属性来修改对象的结构。此外,静态横切可以把扩展和实现附加到对象的基本结构中
2.基于半配置半注解的AOP(依赖在最底下)
配置
<!--扫描的包--> <context:component-scan base-package="cn.test.service"/> <!--开启aop注解,proxy-target-class属性设置为true表示强制使用CGLIB代理--> <aop:aspectj-autoproxy/>
被切入方法
@Component public class AOPTest { //验证后置方法在方法执行之后,返回值之前执行 public int add(int a,int b){ System.out.println("add***"); return a+b; } public Object sub(int a,int b){//该实例是在环绕通知中使用,所有返回值是object类型,否则报异常 System.out.println("sub***"); return a-b; } public int mul(int a,int b){ int i=10/0; return a-b; } }
切面
package com.example.demo; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Service; @Aspect @Service public class AOPInfo { /*重用切入表达式,@Pointcut注解方法的访问权限符(public,private等)控制着重用切入表达式的可见性, 声明为 public时,在不同的包下需要写包名,相同的包下不用写包名*/ @Pointcut("execution(* com.example.demo.*.*(..))") public void PointCut(){ System.out.println("PointCut--"); } //前置通知 //使用重用表达式,PointCut()是注解 @Pointcut的方法名,也具有JoinPoint @Before("PointCut()") public void before(){ System.out.println("before--"); } //后置通知,不管方法是否抛出异常,都会执行这个方法。在方法执行值,返回值之前执行。 @After("PointCut()") public void after(JoinPoint joinPoint){ String name = joinPoint.getSignature().getName();//获取方法名 System.out.println(name); Object[] args = joinPoint.getArgs();//获取参数 for (Object arg : args) { System.out.println(arg); } System.out.println("after--"); } //返回通知,有返回值 @AfterReturning(value = "PointCut()",returning ="result") public void afterReturning(JoinPoint joinPoint, Object result){ String name = joinPoint.getSignature().getName(); System.out.println("afterReturning--"+name+":"+result); } //异常通知 @AfterThrowing(value = "PointCut()",throwing = "ex") public void afterThrowing(JoinPoint joinPoint, Exception ex){ String name = joinPoint.getSignature().getName();//获取方法名 System.out.println("afterThrowing--"+name+":"+ex); } //环绕通知,被拦截的方法的返回值类型需要是object类型,否则报错,其他通知没有这个特征 @Around(value = "PointCut()") public void around(ProceedingJoinPoint joinPoint){ String name = joinPoint.getSignature().getName();//获取方法名 System.out.println(name+":before--");//前置通知 try { System.out.println(name+":after--");//后置通知,后置写在这是不合适,更加适合放在finally中 Object result = joinPoint.proceed();//获取返回值 System.out.println("afterReturning--"+name+":"+result);//返回通知 } catch (Throwable throwable) { System.out.println("afterThrowing--"+name+":"+throwable);//异常通知 } System.out.println("around--");//环绕通知 } }
备注:
ProceedingJoinPoint类的方法:
Getargs方法 获取目标方法的参数
Getsignature方法:返回切入表达式的相关信息
Getarget方法:换回切入表达式的对象
Getthis:返回aop框架为目标对象生成的代理对象
proceed():获取返回值
切入点表达式的语法
完整的语法:Execution (“modifiers-pattern ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)throw-pattern”)
说明:execution:匹配执方法的连接点
modifiers-pattern 权限修饰符 可省略
ret-type-pattern 返回值类型 支持通配
declaring-type-pattern指定方法所属的类 支持通配 可省略//例如:@Before("execution(* spring2..*(int, int))")
name-pattern方法名 支持通配
param-pattern ..代表两个任意的参数假如是*则是代表一个任意类型的参数
throw-pattern 声明抛出异常 支持通配 可省略
多个表达式之间使用 ||,or表示 或,使用 &&,and表示 与,!表示 非
例如:@Before("execution(* spring2.*.*(int, int))")第一个*表示返回类型 第二个*表示类名 第三个表示方法
3.完全依赖配置的AOP
配置
<?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: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/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"> <bean class="cn.test.service.AOPTest"></bean> <bean id="aspect" class="cn.test.service.AOPInfo1"/> <!--aop:config proxy-target-class属性设置为true表示强制使用CGLIB代理--> <aop:config> <aop:aspect ref="aspect" order="2"> <!--只能有一个表达式--> <aop:pointcut id="add" expression="execution(* cn.test.service..add(..))"/> <aop:before method="before" pointcut-ref="add"/> <aop:after method="after" pointcut-ref="add"/> <aop:after-returning method="afterReturning" returning="result" pointcut-ref="add"/> <aop:after-throwing method="afterThrowing" throwing="ex" pointcut="execution(* cn.test.service..mul(..))"/> <aop:around method="around" pointcut="execution(* cn.test.service..sub(..))"></aop:around> </aop:aspect> </aop:config> </beans>
被切入方法
public class AOPTest { public int add(int a,int b){ return a+b; } public Object sub(int a,int b){//该实例是在环绕通知中使用,所有返回值是object类型,否则报异常 return a-b; } public int mul(int a,int b){ int i=10/0; return a-b; } }
切面
public class AOPInfo1 { public void before(){ System.out.println("before--"); } public void after(){ System.out.println("after--"); } public void afterReturning(JoinPoint joinPoint,Object result){ String name = joinPoint.getSignature().getName(); System.out.println("afterReturning--"+name+":"+result); } public void afterThrowing(JoinPoint joinPoint, Exception ex){ String name = joinPoint.getSignature().getName();//获取方法名 System.out.println("afterThrowing--"+name+":"+ex); } public void around(ProceedingJoinPoint joinPoint){ String name = joinPoint.getSignature().getName();//获取方法名 System.out.println(name+":before--");//前置通知 try { System.out.println(name+":after--");//后置通知 Object result = joinPoint.proceed();//获取返回值 System.out.println("afterReturning--"+name+":"+result);//返回通知 } catch (Throwable throwable) { System.out.println("afterThrowing--"+name+":"+throwable);//异常通知 } System.out.println("around--");//环绕通知 } }
4.完全基于注解的AOP
配置
@Configuration//表示为配置 @EnableAspectJAutoProxy//开启事务 @ComponentScan(value = {"cn.test.service"}) public class AOPConfig { }
被切入方法与切面引用的基于半配置半注解的AOP
测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {AOPConfig.class}) public class Test4 { @Autowired private AOPTest aop; @Test public void test(){ aop.add(1, 2); } @Test public void test1(){ aop.sub(3, 2); } @Test public void test2(){ aop.mul(1, 2); } }
5.web加载spring配置
5.1 基于web.xml配置的方式加载
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:application1.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
5.2基于web的ServletContextListener 监听器
配置监听器
public class ServletListen implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext(AOPConfig.class);//基于类 ClassPathXmlApplicationContext cpac = new ClassPathXmlApplicationContext("classpath:application1.xml");//基于配置文件 } @Override public void contextDestroyed(ServletContextEvent sce) { } }
添加监听器
在servlet8.2.4中看到在在meta-inf建立services下建立一个javax.servlet.ServletContainerInitializer文件,需要实现ServletContainerInitializer接口,否则会出现异常
javax.servlet.ServletContainerInitializer文件的内容,全类名的方式,默认servlet运行时会自动加载
添加监听器(实现ServletContainerInitializer接口后,会调用onStartup方法)
@HandlesTypes({})//满足@HandlesTypes的类型的子类,实现类的类型(不包含自身),放入Set<Class<?>> c中,按需添加该注解 public class MyServletContainerInitializer implements ServletContainerInitializer { @Override public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException { ctx.addListener("cn.test.listener.ServletListen"); } }