6 Spring实现AOP(Aspect Oriented Programming)是依赖JDK动态代理和CGLIB代理(不同情况spring会自己选择一种方式)。
JDK动态代理和CGLIB代理的对比:
JDK动态代理:其代理对象必须是某个接口的实现,它是通过在运行期间创建一个接口的实现类来完成对目标对象的代理。
CGLIB代理:实现原理类似于JDK动态代理,只是它在运行期间生成的代理对象是针对目标类扩展的子类。CGLIB是高效的代码生成包,底层是依靠ASM(开源的java字节码编辑类库)操作字节码实现的。
所以spring会有以下俩种选择动态代理实现方式的情况:
*如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
*如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间自动选择;
7 认识AOP中的一些基本概念,然后在一个一个的例子中,不断的加强对这些概念的理解,同时要能自己表述出每个概念的含义
AOP 面向切面编程
目标方法 目标类中需要被执行的方法
aspect 切面/切面类
封装方法,需要通知带到连接点/切入点 前/后执行
joinPoint 连接点
在spring的aop中只有 类中的方法 可以做连接点,每一个方法都可以是一个连接点.
pointCut 切入点 增强器
一组连接点的集合
<aop:pointCut>
advice 通知/拦截器 : 携带切面 到目标方法执行前后
用来控制切面类将来到底是织入到切入点的前面、后面或者是抛异常的时候。
adivsor 增强器/选择器 : 把连接点 变成切入点
用来筛选类中的哪些方法是我们的连接点(哪些方法需要被拦截).
target 目标对象
proxy 代理对象
weaving 织入 :
advice分五种:
把切面类织入到切入点(一堆连接点)
1,前置通知(Before advice):
实现接口MethodBeforeAdvice
在某连接点(join point)之前执行的通知
2,返回后通知(After returning advice):
实现接口AfterReturningAdvice
在某连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
3,抛出异常后通知(After throwing advice):
实现接口ThrowsAdvice
在方法抛出异常退出时执行的通知。
4,后置通知(After (finally) advice):
实现接口AfterAdvice
当某连接点退出的时候执行的通知
5,环绕通知(Around Advice):
实现接口MethodInterceptor
包围一个连接点(join point)的通知,例如事务的处理,就需要这样的通知,因为事务需要在方法前开启,在方法后提交
7 在Spring中,Advice是由spring中的几个接口来指定,主要有以下几种:
1) Before Advice 在目标方法执行前被 切面类 织入
普通的类变成前置通知:实现MethodBeforeAdvice接口
MethodBeforeAdvice接口:自定义类实现了这个接口 spring就会认为你是前置通知
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method m, Object[] args, Object target) throws Throwable;
}
2) Afteradvice 在目标方法执行完成以后被 织入
让一个类变成后置通知:实现AfterReturningAdvice接口
public interface AfterReturningAdvice extends Advice {
void afterReturning(Object returnValue, Method m, Object[] args, Object target) throws Throwable;
}
注意:另外一个返回后通知接口:AfterReturningAdvice的使用方式和这个是类似的,但是需要注意它们俩个之间的区别
3) 环绕Advice: 在目标方法执行之前和之后都 织入
实现MethodInterceptor接口
public interface MethodInterceptor extends Interceptor {
Object invoke(MethodInvocation invocation) throws Throwable;
}
4) Throws Advice 在目标方法执行抛异常的时候 织入
实现ThrowsAdvice接口
序列化
//ThrowsAdvice 是一个空接口,起标识作用
public interface ThrowsAdvice extends Advice {
}
例如:
public class ThrowingAdvice implements ThrowsAdvice {
private MyLogger logger;
public MyLogger getLogger() {
return logger;
}
public void setLogger(MyLogger logger) {
this.logger = logger;
}
//这里这个方法的名字一定要叫afterThrowing
//参数可以是1个也可以是四个
//1个参数的时候只能是一个异常类型的参数
//如果是4个参数的话,参数的顺序也一定要是下面的顺序
public void afterThrowing(Method method, Object[] args, Object target,Exception e) {
logger.log(e.getMessage());
}
//下面这样写也可以
/*
public void afterThrowing(Exception e) {
logger.log(e.getMessage());
}
*/
get/set
}
配置xml文件:
<!-- 配置切面类 -->
<bean name="logger" class="com.briup.aop.aspect.MyLogger"></bean>
<!-- 配置advice -->
<bean name="throwAdvice" class="com.briup.aop.throwException.ThrowingAdvice">
<!-- 注入切面类对象 -->
<property name="logger" ref="logger"></property>
</bean>
<!-- 配置dao层对象 -->
<bean id="dao" class="com.briup.aop.dao.AccountDaoImpl"/>
<!-- 配置目标对象 -->
<bean name="target"
class="com.briup.aop.service.AccountServiceImpl">
<property name="accountDao" ref="dao"></property>
</bean>
<!-- 配置代理对象 -->
<!-- 这里使用的是spring的一个代理对象工厂类产生的 -->
<bean name="proxy"
class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 注入目标对象 -->
<property name="target" ref="target"></property>
<!-- 注入目标对象所实现的接口 可以有多个接口 -->
<property name="proxyInterfaces">
<list>
<value>com.briup.aop.service.IAccountService</value>
</list>
</property>
<!-- 注入advice 可以有多个 -->
<property name="interceptorNames">
<list>
<value>throwAdvice</value>
</list>
</property>
</bean>
8 advisor
作用:筛选要拦截目标对象的方法,之前的advice是把目标对象中的所有方法全部都进行代理
不要写java代码 ,只要配置
指定为advisor的接口为:
public interface PointcutAdvisor {
Pointcut getPointcut();
Advice getAdvice();
}
spring中已经给我们提供了实现类RegexpMethodPointcutAdvisor,在xml中直接配使用就可以了
xml配置文件:
<!-- 配置切面类 -->
<bean name="logger" class="com.briup.aop.aspect.MyLogger"></bean>
<!-- 配置advice -->
<bean name="beforeAdvice"
class="com.briup.aop.before.BeforeAdvice">
<!-- 注入切面类对象 -->
<property name="logger" ref="logger"></property>
</bean>
<!-- 配置advisor 增强器-->
<!-- 作用:筛选要拦截(要代理)的方法 -->
<bean name="advisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<!-- 注入advice 表示增强器要在哪一个advice起作用-->
<property name="advice" ref="beforeAdvice"></property>
<!-- 注入需要被拦截的目标对象中的方法(连接点) -->
<property name="patterns">
<list>
<value>.*bankAction</value> 这个地方 .*bank.* 就是以bank开头 或者这个结尾 但是这个bank在中间就不可以
</list>
</property>
</bean>
<bean id="dao"
class="com.briup.aop.dao.AccountDaoImpl"/>
<!-- 配置目标对象 -->
<bean name="target"
class="com.briup.aop.service.AccountServiceImpl">
<property name="accountDao" ref="dao"></property>
</bean>
<!-- 配置代理对象 -->
<!-- 这里使用的是spring的一个代理对象工厂类产生的 -->
<bean name="proxy"
class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 注入目标对象 -->
<property name="target" ref="target"></property>
<!-- 注入目标对象所实现的接口 可以有多个接口 -->
<property name="proxyInterfaces">
<list>
<value>com.briup.aop.service.IAccountService</value>
</list>
</property>
<!-- 注入advice/advisor 可以有多个 -->
<property name="interceptorNames">
<list>
<value>advisor</value>
</list>
</property>
</bean>
9 AutoProxy 自动代理:DefaultAdvisorAutoProxyCreator类的使用
使用原因:在配置文件中我们往往需要给很多个目标对象设置代理对象,那么上面例子的方式就需要每个目标对象的代理对象都需要配置一套类似的标签
自动代理:可以用很少的配置为xml文件中的目标对象自动的生成对应的代理对象
使用自动代理:
1,必须有增强器advisor,只有在增强器advisor中配置了哪些方法需要被拦截再然后织入
2,自动代理类(Spring)就会去IOC容器中找哪些bean中有增强器所拦截织入的方法。就代理这样的bean
xml配置文件:
<!-- 配置切面类 -->
<bean name="logger" class="com.briup.aop.aspect.MyLogger"></bean>
<!-- 配置advice -->
<bean name="beforeAdvice"
class="com.briup.aop.before.BeforeAdvice">
<!-- 注入切面类对象 -->
<property name="logger" ref="logger"></property>
</bean>
<!-- 配置advisor -->
<!-- 作用:筛选要拦截的方法 -->
<bean name="advisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<!-- 注入advice -->
<property name="advice" ref="beforeAdvice"></property>
<!-- 注入需要被拦截的目标对象中的方法 -->
<property name="patterns">
<list>
<value>.*bankAction</value>
<value>.*deposit</value>
<value>.*withdraw</value>
</list>
</property>
</bean>
<bean id="dao" class="com.briup.aop.dao.AccountDaoImpl"/>
<!-- 配置目标对象 -->
<bean name="target"
class="com.briup.aop.service.AccountServiceImpl">
<property name="accountDao" ref="dao"></property>
</bean>
<bean name="target2"
class="com.briup.aop.service.AccountServiceImpl">
<property name="accountDao" ref="dao"></property>
</bean>
<bean name="target3"
class="com.briup.aop.service.AccountServiceImpl">
<property name="accountDao" ref="dao"></property>
</bean>
<!-- 配置代理对象 -->
<!-- 这里使用自动代理的方式 autoproxy -->
<!-- 注意:这不是一个工厂类,所以不能用过proxy来拿代理对象 -->
<bean name="proxy"
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
</bean>
<!--
使用自动代理的时候需要注意的方面:
1.当前的配置里面一定要有一个advisor的配置
2.不需要向自动代理类中注入任何信息
3.不管目标对象是否实现了一个或多接口,自动代理的方式
都能够为它产生代理对象(CGLib的方式).
4.从spring容器中拿代理对象的时候,需要通过目标对象的
名字来拿。
-->
10 AutoProxyByName 通过bean名字进行自动代理:BeanNameAutoProxyCreator类的使用
使用原因:虽然自动代理可以很方便的给xml文件中的目标对象设置对应的代理对象,但是并不是xml文件中的所有对象都是我们的目标对象,我们更想希望可以进一步筛选出某几个对象为我们的目标对象
通过bean名字进行自动代理:解决了上面的问题,给我们提供了筛选目标对象的配置方式
xml配置文件:
<!-- 配置切面类 -->
<bean name="logger" class="com.briup.aop.aspect.MyLogger"></bean>
<!-- 配置advice -->
<bean name="beforeAdvice"
class="com.briup.aop.before.BeforeAdvice">
<!-- 注入切面类对象 -->
<property name="logger" ref="logger"></property>
</bean>
<!-- 配置advisor -->
<!-- 作用:筛选要拦截的方法 -->
<bean name="advisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<!-- 注入advice -->
<property name="advice" ref="beforeAdvice"></property>
<!-- 注入需要被拦截的目标对象中的方法 -->
<property name="patterns">
<list>
<value>.*bankAction</value>
<value>.*deposit</value>
<value>.*withdraw</value>
</list>
</property>
</bean>
<bean id="dao"
class="com.briup.aop.dao.AccountDaoImpl"/>
<!-- 配置目标对象 -->
<bean name="target"
class="com.briup.aop.service.AccountServiceImpl">
<property name="accountDao" ref="dao"></property>
</bean>
<bean name="target2"
class="com.briup.aop.service.AccountServiceImpl">
<property name="accountDao" ref="dao"></property>
</bean>
<bean name="target3"
class="com.briup.aop.service.AccountServiceImpl">
<property name="accountDao" ref="dao"></property>
</bean>
<!-- 配置代理对象 -->
<!-- 这里使用自动代理的方式 autoproxybyname -->
<bean name="proxy" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<!-- 注入需要被代理的对象名字 -->
<property name="beanNames">
<list>
<value>target</value>
<value>target2</value>
<value>dao</value>
</list>
</property>
<!-- 注入advice或者advisor -->
<property name="interceptorNames">
<list>
<value>advisor</value>
</list>
</property>
</bean>
<!--
使用自动代理的时候需要注意的方面:
1.当前的配置里面有没有advisor的配置都没关系
2.需要向自动代理类中注入被代理目标对象的名字已经advice或者advisor
3.不管目标对象是否实现了一个或多接口,自动代理的方式
都能够为它产生代理对象.
4.从spring容器中拿代理对象的时候,需要通过目标对象的
名字来拿。
-->
第四天:
1 aop:config标签:主要是用来配置自动代理
1,加入schame文件
2,加入相应jar包
3,xml文件需要加入几句话...具体看文档
使用aop的专用标签来完成相关的配置.
需要引入一些相应的schame文件spring-aop-3.2.xsd
<aop:config> : 配置代理对象
筛选出切入点
把相应的切面带到切入点
1,<aop:pointcut>+<aop:advisor>
2,<aop:pointcut>+<aop:aspect>+<aop:after method=""/>
其中主要表现是使用Aspect的expression的操作:
execution(表达式):主要用来筛选连接点-->切入点
方法
权限修饰符 返回值 包 方法名 参数列表
public * com.briup.service.*.*(*)
表达式:
1,返回类型模式
* 任意的返回类型 返回类型模式决定了方法的返回类型必须依次匹配一个连接点。
2,名字模式
* 所有或者部分命名模式 名字模式匹配的是方法名,
3,参数模式
() 没有参数的方法
(..) 参数任意的方法
(*) 一个参数的方法
(*,String) 匹配两个参数,第一个参数任意,第二个必须为String
注意在使用之前需要在xml文件的beans标签中加入新的schame文件:并在Eclipse中进行关联配置
<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"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">
下面给出一些常见切入点表达式的例子。
1)任意包下的任意类中的公共方法的执行:
execution(public * *(..)) 公有的 返回值不限 方法名不限 参数任意
2)任何一个以“set”开始的方法的执行:
execution(* set*(..)) 所有的以set开头 参数任意的方法
3)AccountService 接口的任意方法的执行:
execution(* com.briup.service.AccountService.*(..)) 所有 这个类中方法参数不限
4)定义在service包里的任意方法的执行:
execution(* com.briup.service.*.find*(..)) 所有 这个包下所有类里面的所有方法
5)定义在service包或者子包里的任意方法的执行:
execution(* com.briup.service..*.*(..)) 所有 包下以及service的子包下所有类下所有方法 参数不限
eg:expression="execution(* com.briup.service.*.*(..)) or execution(* com.shiyue.service.*.*(..))"
筛选连接点-->切入点
注意: 1.从spring容器中拿代理对象的时候也是要用目标对象的名字来拿。
2.没有实现任何接口的目标对象也能产生代理对象。
<!-- 配置aop的代理 -->
<aop:config>
<!-- 定义一个切入点 并给切入点起名为myPointCut -->
<!-- 切入点是一组连接点的集合 -->
<aop:pointcut expression="execution(public * com.briup.aop.service.*.*(..))" id="myPointCut"/>
<!-- 定义哪一个advice在哪一个切入点上面起作用 -->
<aop:advisor advice-ref="beforeAdvice" pointcut-ref="myPointCut" />
</aop:config>
织入: 切面 --> 切入点
<!--
expression="execution(public * com.briup.aop.service.*.*(..))"
这个引号""里面就是用表达式的方式来定义切入点,只要是符合我们这个表达式要求的
方法就是我们的连接点,连接点的集合就是我们要定义的切入点。
表达式中从左到右的*号:
第一个* 表示方法的返回类型不限。
第二个* 表示包中的任意一个类
第三个* 表示类中的任意一个方法
同时方法的参数也没有限制.
-->
2 在一个切面类中定个多个方法,根据xml文件的配置每个方法都可以织入到切入点的不同位置,并且advice是在aop的标签中进行配置,不需要再写对应的advice类了
例如:
//这个类相当于我们之前的切面类
//只不过这个切面类中有很多方法都可以织入到切入点上面
//我们可以控制把这里的任何一个方法织入到任何一个切入点上面
public class XmlHandler {
public void beforeTest(JoinPoint p){
System.out.println(p.getSignature().getName()+" before...");
}
public void afterTest(JoinPoint p){
System.out.println(p.getSignature().getName()+" after...");
}
public void afterReturningTest(JoinPoint p){
System.out.println(p.getSignature().getName()+" afterReturning");
}
//在和aroundAdvice结合的时候,这个方法一定要加上这个ProceedingJoinPoint类型的参数
public Object aroundTest(ProceedingJoinPoint pjp)throws Throwable{
//JoinPoint对象不能调用连接点所表示的方法
//ProceedingJoinPoint能调用连接点所表示的方法 pjp.proceed()
System.out.println(pjp.getSignature().getName()+" is start..");
//调用到连接点方法
Object obj = pjp.proceed();
System.out.println(pjp.getSignature().getName()+" is end..");
return obj;
}
public void throwingTest(JoinPoint p,Exception ex){
System.out.println(p.getSignature().getName()+" is throwing..."+ex.getMessage());
}
}
xml文件配置:
<!-- 配置dao层对象 -->
<bean id="dao"
class="com.briup.aop.dao.AccountDaoImpl"/>
<!-- 配置目标对象 -->
<bean name="target"
class="com.briup.aop.service.AccountServiceImpl">
<property name="accountDao" ref="dao"></property>
</bean>
<!-- 配置切面类 -->
<bean name="handler" class="com.briup.aop.xml.XmlHandler"></bean>
<!-- 配置aop的代理 -->
<aop:config>
<!-- 定义切入点名为myPointCut -->
<aop:pointcut expression="execution(public * com.briup.aop.service.*.*(..))" id="myPointCut"/>
<!-- 定义切面类 以及需要使用的advice -->
<aop:aspect id="aspect" ref="handler">
<!-- 表示beforeAdvice会把切面类handler中的beforeTest方法织入到名字叫myPointCut的切入点上面 -->
<aop:before method="beforeTest" pointcut-ref="myPointCut"/>
<!-- after表示不管方法是否正常结束都会起作用 -->
<aop:after method="afterTest" pointcut-ref="myPointCut"/>
<!-- after-returning表示方法正常结束才会起作用(抛异常时候不起作用) -->
<aop:after-returning method="afterReturningTest" pointcut-ref="myPointCut"/>
<aop:around method="aroundTest" pointcut-ref="myPointCut"/>
<!-- throwing="ex"表示throwingTest方法中接收异常对象的名字一定要是ex -->
<aop:after-throwing method="throwingTest" pointcut-ref="myPointCut" throwing="ex"/>
</aop:aspect>
</aop:config>
3 使用注解配置AOP:其实就是在上面的类XmlHandler中加入上注解然后去掉xml中的aop标签配置,这里把类改名为AnnotationHandler,
切面类:@Aspect
写在类上
切入点: @Pointcut 空方法是@载体
写一个空方法:因为@Pointcut-->id-->空方法名()
通知: @Before,@After,@AfterReturning,@Around
切面的方法
<aop:aspectj-autoproxy/>:如果使用了AOP有关注解 需要这个标签标示
例子:
@Component
@Aspect//标示这个类是切面类 写在类上
public class AnnotationHandler {
/*
* 在一个方法上面加上注解来定义切入点
* 这个切入点的名字就是这个方法的名字
* 这个方法本身不需要有什么作用
* 这个方法的意义就是:给这个 @Pointcut注解一个可以书写的地方
* 因为注解只能写在方法、属性、类的上面,并且方法名作为切入点的名字
* @Pointcut 注解 写在方法上,""中写切入点表达式
* */
@Pointcut("execution(public * com.briup.aop.service..*.*(..))")
public void myPointCut(){}
//注:这里面的所有方法的JoinPoint类型对象都可以去掉不写
@Before("myPointCut()")// 前置通知 携带下面方法 织入到切入点
public void beforeTest(JoinPoint p){
System.out.println(p.getSignature().getName()+" before...");
}
/*
* @After和@AfterReturning
*
* @After标注的方法会在切入点上的方法结束后被调用(不管是不是正常的结束).
* @AfterReturning标注的方法只会在切入点上的方法正常结束后才被调用.
* */
@After("myPointCut()")
public void afterTest(JoinPoint p){
System.out.println(p.getSignature().getName()+" after...");
}
@AfterReturning("myPointCut()")
public void afterReturningTest(JoinPoint p){
System.out.println(p.getSignature().getName()+" afterReturning");
}
@Around("myPointCut()")
public Object aroundTest(ProceedingJoinPoint pjp)throws Throwable{
System.out.println(pjp.getSignature().getName()+" is start..");
//调用连接点的方法去执行
Object obj = pjp.proceed();
System.out.println(pjp.getSignature().getName()+" is end..");
return obj;
}
//在切入点中的方法执行期间抛出异常的时候,会调用这个 @AfterThrowing注解所标注的方法
@AfterThrowing(value="myPointCut()",throwing="ex")
public void throwingTest(JoinPoint p,Exception ex){
System.out.println(p.getSignature().getName()+" is throwing..."+ex.getMessage());
}
}
xml配置:注意给例子中使用的其他的类上面也使用注解
<aop:aspectj-autoproxy/>
<context:component-scan base-package="com.briup.aop"/>
Day3-note
aop :
面向切面编程
切面 :项目中很多具有共同特点方法的集合
核心 : 代理
静态代理
目标类 和 代理类 都事先准备好
动态代理
目标类 有
代理类 代码运行期间 动态生成
jdk : 基于接口,
代理类和目标类 实现相同的接口
看起来方法一样
cglib : 基于继承
代理类 继承 目标类
spring aop 编程 集成了 jdk 和 cglib 两种代理
spring会自动选择
目标类 : 实现了接口 spring选jdk
目标类 : 没有实现了接口 spring选cglib
aop 基础名词 :
目标类(委托类)
目标对象
目标方法
代理类
代理对象
joinPoint : 连接点
目标类中所有方法都是连接点
pointcut : 切入点
目标类中部分方法集合是切入点
ascept : 切面类
代码封装在这个类中
advice :通知/拦截器
把代码 携带 到目标方法前后 执行
advisor : 增强器
把通知加强
把代码 携带 到 【部分】 目标方法前后 执行
wave : 织入
通知 携带 代码 去 目标方法 前后执行的过程
目标类 :
1,核心业务逻辑 + 非核心业务逻辑
2,目标类中的方法 不想完成 非核心业务逻辑
3,非核心业务交给spring处理
service 业务逻辑
日志记录
提交事务 回滚事务
advice :通知
1,前置通知(Before advice):
实现接口MethodBeforeAdvice
在某连接点(join point)之前执行的通知
2,返回后通知(After returning advice):
实现接口AfterReturningAdvice
在某连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
3,抛出异常后通知(After throwing advice):
实现接口ThrowsAdvice
在方法抛出异常退出时执行的通知。
4,后置通知(After (finally) advice):
实现接口AfterAdvice
当某连接点退出的时候执行的通知
5,环绕通知(Around Advice):
实现接口MethodInterceptor
包围一个连接点(join point)的通知,例如事务的处理,就需要这样的通知,因为事务需要在方法前开启,在方法后提交
AOP 编程 需要 配置得到代理对象
使用代理对象 调用 目标方法
增强器 :对代理的目标方法进行控制
把通知加强