目录
一、Spring的AOP概述
二、Spring AOP快速入门(全xml配置)
1)Schema-based实现步骤
2)AspectJ实现步骤(推荐)
三、Spring AOP的注解方式(半xml和半注解,基于 Aspect)
四、Spring的AOP全注解(理解)
五、动态代理的简单理解
一、Spring的AOP概述
1、AOP: 面向切面编程思想
2、本质理解: 将一些共性的内容进行抽取,在需要用到的地方,以动态代理的方式进行插入。
3、好处:在不修改源码的基础上,还能对源码进行前后的增强。高扩展性。
4、底层实现的技术: 动态代理。spring就是把动态代理进行层层封装 诞生出了aop思想。
动态代理的实现有两种:JDK动态代理 和 CGLIB动态代理
①JDK动态代理 --不用导包,jdk提供好了
proxy
条件: 目标类必须得有接口
②CGLIB动态代理---第三方 单用它就必须导包(导包cglib.jar和asm.jar--字节码解析工具包。但在spring里面使用不需单独导包,因为spring-core.jar里面已经整合)
enhance
条件: 只要有一个目标类即可增强
③spring如何均衡这两种:
如果目标类有接口 会默认使用jdk的动态代理
如果目标类没有接口 会默认使用CGLIB的动态代理
aop是oop的延续,用于实现oop不太适合实现的代码,比如:日志。
ps:对象增强的手段:
①继承 如:httpServletRequest request就不能被继承; 因为是tomcat帮忙创建出来的,不知道父类是谁
缺点: 需要知道要继承的父类是谁
②装饰者模式
缺点: 需要有接口 save() 100个方法
这个接口除了要增强的方法以外,剩余的方法也都得实现
③动态代理
jdk的动态代理 save() 100个方法
需要有接口 可以指定只增强这个接口下的某个方法
cglib的动态代理
优点:不需要有接口 也可以指定增强方法
缺点:代码写的较为复杂
5、常用概念
- 1.PointCut 切点/切入点:被增强的方法。例如:save()
- 2.advice 通知/增强,增强代码。包括:前置通知@Before; 后置通知@AfterReturning; 异常通知@AfterThrowing等
-
3.Aspect(切面): 是切点pointcut和通知advice的总称。切面=切点+通知/增强
一个线是一个特殊的面。
一个切点和一个通知,组成一个特殊的面。
- 4. Weaving(织入):是指把增强advice应用到目标对象target来创建新的代理对象proxy的过程.简单来说,把切面添加到原有功能的过程。是一个动作。
- 5.Joinpoint 连接点:可以被增强的方法。例如:impl类里的所有方法都可以叫做连接点
- 6.target:目标类,需要被代理的类。例如:UserService
6、AOP的应用:
- 权限拦截
- 日志的输出
- 性能的检测
- 事务管理
二、Spring AOP快速入门(全xml配置)
spring提供了两种AOP的实现方式:
1)Schema-based
①每个通知都需要实现接口或类(如:前置通知需实现MethodBeforeAdvice接口一样)
② 配置 spring 配置文件时在<aop:config>配置
2)AspectJ(推荐)
①每个通知不需要实现接口或类
②配置 spring 配置文件是在<aop:config>的子标签<aop:aspect>中配置
1)Schema-based实现步骤
1、导包
aopalliance.jar(AOP联盟,即AOP的一套规范(接口) )
aspectj.jar(第三方--实现了AOP的一套规范)
Spring-aop.jar(实现了AOP的一套规范)
spring-aspects.jar (spring整合aspectj)
2、新建通知类
2.1 前置通知,实现MethodBeforeAdvice接口
arg0: 切点方法对象 + Method 对象
arg1: 切点方法参数
arg2: 切点在哪个对象中
2.2 后置通知,实现AfterReturningAdvice接口
arg0: 切点方法返回值 ---比前置对象多一个参数
arg1: 切点方法对象
arg2: 切点方法参数
arg3: 切点方法所在类的对象
2.3 异常通知,实现ThrowsAdvice接口
API不提供重写方法,我们必须自己写方法,且方法名必须叫 afterThrowing
方法有两种实现,参数必须是 1 个或 4 个
异常类型要与切点报的异常类型一致
2.4 环绕通知,实现MethodInterceptor接口
环绕通知 = 前置通知 + 后置通知
有了环绕通知就不需要再写前置通知和后置通知了
3、配置 spring 配置文件
3.1 引入 aop 命名空间
3.2 确定目标类 (目标类中有切入点 ---要被增强的方法)
3.3 确定切面类 (里面有通知/增强 ----增强的那段代码方法)
3.4 配置织入过程 (将增强方法和被增强方法进行结合)
<aop:pointcut id="pointcut" expression="execution(* com.ly.spring.service.impl.*.*(..))" />
表达式:第一个*是返回值 第二个*是包名/子包名 第三个*是类名 第四个*是方法名
* : 通配符,代表可以匹配任意方法名,任意类名,任意一级包名
.. :代表匹配任意方法参数
<?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="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 目标类 (有切入点 有被增强的方法demo1) -->
<bean id="personService" class="com.ly.spring.service.PersonService"></bean>
<bean id="userService" class="com.ly.spring.service.impl.UserServiceImpl"></bean>
<!-- 配置通知类对象,在切面中引入 -->
<bean id="mybefore" class="com.ly.spring.advice.MyBeforeAdvice"></bean>
<bean id="myafterReturning" class="com.ly.spring.advice.MyAfterReturningAdvice"></bean>
<bean id="myaround" class="com.ly.spring.advice.MyAround"></bean>
<bean id="mythrows" class="com.ly.spring.advice.MyThrowsAdvice"></bean>
<!-- 配置切面 -->
<aop:config>
<!-- 配置切点 -->
<aop:pointcut expression="execution(* com.ly.spring.service.PersonService.*(..))" id="pointcut1"/>
<aop:pointcut expression="execution(* com.ly.spring.service.impl.UserServiceImpl.*(..))" id="pointcut2"/>
<!-- <aop:pointcut expression="execution(void com.ly.spring.service.PersonService.save())" id="pointcut1"/>
<aop:pointcut expression="execution(void com.ly.spring.service.PersonService.delete())" id="pointcut2"/>
<aop:pointcut expression="execution(* com.ly.spring.service.PersonService.up*(..))" id="pointcut3"/>
<aop:pointcut expression="execution(* com.ly.spring.service.*.find(..))" id="pointcut4"/>
<aop:pointcut expression="execution(* com.ly.spring.service.PersonService.*(..))" id="pointcut5"/>
<aop:pointcut expression="execution(* com.ly.spring.service.impl.UserServiceImpl.*(..))" id="pointcut6"/> -->
<!-- 通知 -->
<aop:advisor advice-ref="mybefore" pointcut-ref="pointcut1"/> <!-- 前置通知 -->
<aop:advisor advice-ref="myafterReturning" pointcut-ref="pointcut1"/> <!-- 后置通知 -->
<aop:advisor advice-ref="myaround" pointcut-ref="pointcut1"/> <!-- 环绕通知 = 前置通知 + 后置通知 -->
<aop:advisor advice-ref="mythrows" pointcut-ref="pointcut1"/> <!-- 异常通知 -->
<!-- 最终通知,暂未定义 -->
<aop:advisor advice-ref="myaround" pointcut-ref="pointcut2"/> <!-- 环绕通知 = 前置通知 + 后置通知 -->
</aop:config>
</beans>
4、测试
2)AspectJ实现步骤(推荐)
1、导包
aopalliance.jar(AOP联盟,即AOP的一套规范(接口) )
aspectj.jar(第三方--实现了AOP的一套规范)
Spring-aop.jar(实现了AOP的一套规范)
spring-aspects.jar (spring整合aspectj)
2、新建通知类,不用实现接口,类中方法名任意
3、配置 spring 配置文件
1) 确定目标类 (目标类中有切入点 ---要被增强的方法)
2 )确定切面类 (里面有通知/增强 ----增强的那段代码方法)
3) 配置织入过程 (将增强方法和被增强方法进行结合)
4)细节:①在<aop:config>的子标签<aop:aspect>中配置
② <aop:pointcut id="pointcut" expression="execution(* com.ly.spring.service.impl.*.*(..))" />
表达式:第一个*是返回值 第二个*是包名/子包名 第三个*是类名 第四个*是方法名
* : 通配符,代表可以匹配任意方法名,任意类名,任意一级包名
.. :代表匹配任意方法参数
5)通知类型:
前置通知 @Before <aop:before method="" pointcut-ref=""/>
后置通知 @AfterReturning
环绕通知 @Around <aop:around method="" pointcut-ref=""/>
异常通知 @AfterThrowing <aop:after-throwing method="" pointcut-ref=""/>
最终通知 @After
<?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="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 目标类 (有切入点 有被增强的方法save) -->
<bean id="personService" class="com.ly.spring.service.PersonService"></bean>
<bean id="userService" class="com.ly.spring.service.impl.UserServiceImpl"></bean>
<!-- 切面类(有通知/增强 有增强方法) -->
<bean id="myAspect" class="com.ly.spring.aspectj.MyAspect"></bean>
<!-- 配置织入(增强方法和被增强方法集成在一起) -->
<aop:config>
<aop:aspect ref="myAspect">
<!-- 定义切入点 -->
<aop:pointcut expression="execution(* com.ly.spring.service.PersonService.*(..))" id="pointcut1"/>
<aop:pointcut expression="execution(* com.ly.spring.service.impl.UserServiceImpl.*(..))" id="pointcut2"/>
<!-- <aop:pointcut expression="execution(void com.ly.spring.service.PersonService.save())" id="pointcut1"/>
<aop:pointcut expression="execution(void com.ly.spring.service.PersonService.delete())" id="pointcut2"/>
<aop:pointcut expression="execution(* com.ly.spring.service.PersonService.up*(..))" id="pointcut3"/>
<aop:pointcut expression="execution(* com.ly.spring.service.*.find(..))" id="pointcut4"/> -->
<!-- 原始方式 -->
<!-- <aop:before method="beforeMethod" pointcut="execution(void com.ly.spring.domain.Person.save())"/>
<aop:after-returning method="aftereturningMethod" pointcut="execution(void com.ly.spring.domain.Person.save())"/> -->
<!--便捷方式 -->
<aop:before method="beforeMethod" pointcut-ref="pointcut1"/> <!-- 前置通知 -->
<aop:after-returning method="afterReturningMethod" pointcut-ref="pointcut1"/> <!-- 后置通知 -->
<aop:around method="aroundMethod" pointcut-ref="pointcut1"/> <!-- 环绕通知 = 前置通知 + 后置通知 -->
<aop:after-throwing method="throwingMethod" pointcut-ref="pointcut1"/> <!-- 异常通知 -->
<aop:after method="afterMethod" pointcut-ref="pointcut1"/> <!-- 最终通知 -->
<aop:around method="aroundMethod" pointcut-ref="pointcut2"/> <!-- 环绕通知 -->
</aop:aspect>
</aop:config>
</beans>
4、测试
三、Spring AOP的注解方式(半xml和半注解,基于 Aspect)
1、导包
aopalliance.jar(AOP联盟,即AOP的一套规范(接口) )
aspectj.jar(第三方--实现了AOP的一套规范)
Spring-aop.jar(实现了AOP的一套规范)
spring-aspects.jar (spring整合aspectj)
2、spring配置文件
2.1 开启注解扫描器
2.2 开启注解的动态代理方式,缺省值false:使用jdk动态代理
<?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="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 开启注解扫描器 -->
<context:component-scan base-package="com.ly.spring"></context:component-scan>
<!-- 开启注解的动态代理方式 为了识别@Before @AfterReturning -->
<!-- proxy-target-class: false:使用jdk动态代理(默认) true:使用cglib动态代理 -->
<!-- <aop:aspectj-autoproxy></aop:aspectj-autoproxy> 这样写也可,使用默认 -->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
</beans>
3、给切面类添加注解
@Component("bean的id") ---定义在类,被Spring管理
@Aspect ----切面类,相当于<aop:aspect ref=""/>表示通知方法在当前类中
@Before(" ") 前置通知、@After最终通知 等五种类型
4、给目标类添加注解
@Service("bean的id")/@Component("bean的id") ---定义在类,被Spring管理
5、测试
四、Spring的AOP全注解(理解)
在半注解半xml的基础上:
1、添加注解类,删除applicationContext.xml配置文件
2、测试
五、动态代理的简单理解
代理模式是23中设计模式之一。又分为静态代理和动态代理。
1、代理设计模式
1.1 真实对象(老总)
1.2 代理对象(秘书)
1.3 抽象对象(抽象功能)
2、代理设计模式优点:
2.1 保护真实对象. ---真实对象是 老总,一切都先经过秘书处理,不让老总的信息过多泄露
2.2 让真实对象职责更明确. ---老总只管做决定这种打算,预约时间这种小事交给秘书
2.3 扩展
3、静态代理设计模式的缺点:
3.1 代理类中每个功能都需要重写
3.2 当代理功能比较多时,代理类中方法需要写很多
4、为了解决静态代理频繁重写代理功能的缺点,产生了动态代理。可分为:
4.1 JDK 提供的
4.2 cglib 动态代理,第三方
5、JDK动态代理
代理类需实现InvocationHandler接口;真实类要实现接口
5.1 优点:jdk 自带,不需要额外导入 jar
5.2 缺点:
5.2.1 真实对象必须实现接口
5.2.2 利用反射机制invoke.效率不高
6、cglib 动态代理
代理类需实现MethodInterceptor接口;真实类不需要实现接口
6.1 cglib 优点:
6.1.1 基于字节码,生成真实对象的子类,运行效率高于 JDK 动态代理.
6.1.2 真实类不需要实现接口
6.2 cglib 缺点:
6.2.1 需要额外导入 jar包 cglib.jar和asm.jar (字节码解析工具包,因为cglib是通过字节码来实现的)
如果cglib用在spring框架里面就不需要导包了,因为spring本身的jar包已经整合了cglib
6.3 使用 spring aop 时,需在applicationContext.xml中添加 <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy> true 使用 cglib; false 使用 jdk(默认值)
否则,容易抛出异常。 Proxy 和真实对象转换异常,如下图: