一、AOP
1)AOP(Aspect Orented Programming) 中文名称为面向切面编程,是Spring的三大特性之一。
正常程序的执行流程是纵向执行流程,AOP是指再原有纵向执行流程中添加横切面。
特点:1.高扩展性,不需要修改原有程序代码
2.原有功能相当于释放了部分逻辑,让指责更加明确。
面向切面编程就是:在程序原有纵向执行流程中,针对某一个或某一些方法添加通知,形成横切面过程。
2)常用概念
原有功能: 切点, pointcut
前置通知: 在切点之前执行的功能. before advice
后置通知: 在切点之后执行的功能,after advice
异常通知:如果切点执行过程中出现异常,会触发异常通知.throws advice
切面:所有功能的总称
织入: 把切面嵌入到原有功能的过程叫做织入
二、AOP的实现方式
Spring提供了2种AOP实现方式
1)Schema-based方式
1.每个通知都需要实现接口或类
2.配置Spring配置文件时再<aop:config>中配置
2)AspectJ方式
1.每个通知不需要实现接口或类
2.配置 spring 配置文件是在<aop:config>的子标签<aop:aspect>中配置
三、Schema-based实现步骤
1)导入 jar
导入aop功能依赖的jar包以及Spring基础jar包
2)新建通知类
1.新建前置通知类
public class MyBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
System.out.println("执行前置通知");
}
}
arg0: 切点方法对象 Method 对象
arg1: 切点方法参数
arg2:切点在哪个对象中
2.新建后置通知类
public class MyAfterAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object arg0, Method arg1,Object[] arg2, Object arg3) throws Throwable
{
System.out.println("执行后置通知");
}
}
arg0: 切点方法返回值
arg1:切点方法对象
arg2:切点方法参数
arg3:切点方法所在类的对象
3)配置Spring配置文件
1.引入 aop 命名空间(去Spring文档中搜索xmlns:aop)
2.配置通知类的<bean>
3.配置切面
4.*通配符,匹配任意方法名,任意类名,任意一级包名
5.如果希望匹配任意方法参数 (..)
<?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.xsd">
<!-- 配置通知类对象,在切面中引入 -->
<bean id="mybefore" class="com.bjsxt.advice.MyBeforeAdvice"></bean>
<bean id="myafter" class="com.bjsxt.advice.MyAfterAdvice"></bean>
<!-- 配置切面 -->
<aop:config>
<!-- 配置切点 -->
<aop:pointcut expression="execution(* com.bjsxt.test.Demo.demo2())" id="mypoint"/>
<!-- 通知 -->
<aop:advisor advice-ref="mybefore" pointcut-ref="mypoint"/>
<aop:advisor advice-ref="myafter" pointcut-ref="mypoint"/>
</aop:config>
<!-- 配置 Demo 类,测试使用 -->
<bean id="demo" class="com.bjsxt.test.Demo"></bean>
</beans>
四、AspectJ方式实现
1)新建一个类,不用实现任何接口,且类名和方法名任意
public class MyAdvice {
public void mybefore(String name1,int age1){
System.out.println("前置"+name1 );
}
public void mybefore1(String name1){
System.out.println("前置:"+name1);
}
public void myaftering(){
System.out.println("后置 2");
}
public void myafter(){
System.out.println("后置 1");
}
public void mythrow(){
System.out.println("异常");
}
public Object myarround(ProceedingJoinPoint p) throws Throwable{
System.out.println("执行环绕");
System.out.println("环绕-前置");
Object result = p.proceed();
System.out.println("环绕后置");
return result;
}
}
2)配置Spring配置文件
<aop:config>
<aop:aspect ref="myadvice">
<aop:pointcut expression="execution(* com.bjsxt.test.Demo.demo1(String,int)) and args(name1,age1)" id="mypoint"/>
<aop:pointcut expression="execution(* com.bjsxt.test.Demo.demo1(String)) and args(name1)" id="mypoint1"/>
<aop:before method="mybefore" pointcut-ref="mypoint" arg-names="name1,age1"/>
<aop:before method="mybefore1" pointcut-ref="mypoint1" arg-names="name1"/>
<!-- <aop:after method="myafter" pointcut-ref="mypoint"/>
<aop:after-returning method="myaftering" pointcut-ref="mypoint"/>
<aop:after-throwing method="mythrow" pointcut-ref="mypoint"/>
<aop:around method="myarround" pointcut-ref="mypoint"/>-->
</aop:aspect>
</aop:config>
<aop:after/> 后置通知,是否出现异常都执行
<aop:after-returing/> 后置通知,只有当切点正确执行时执行
<aop:after/> 和 <aop:after-returing/> 和 <aop:after-throwing/>执行顺序和配置顺序有关
execution() 括号不能扩上 args
中间使用 and 不能使用&& 由 spring 把 and 解析成&&
args(名称) 名称自定义的顺序和 demo1(参数,参数)对应
<aop:before/> arg-names=” 名 称 ” 名 称 来 源 于 expression=”” 中 args(),名称必须一样,args() 有几个参数,arg-names 里面必须有几个参数,arg-names=”” 里面名称必须和通知方法参数名对应。
五、配置异常通知(Schema-based与AspectJ方式)
1)Schema-based方式
1.新建一个类实现 throwsAdvice 接口
必须自己写方法,且必须叫 afterThrowing
异常类型要与切点报的异常类型一致
public class MyThrow implements ThrowsAdvice{
// public void afterThrowing(Method m, Object[] args, Object target, Exception ex) {
// System.out.println("执行异常通知");
// }
public void afterThrowing(Exception ex) throws Throwable {
System.out.println("执行异常通过-schema-base 方式");
}
}
在ApplicationContext.xml中配置
<bean id="mythrow" class="com.bjsxt.advice.MyThrow"></bean>
<aop:config>
<aop:pointcut expression="execution(* com.bjsxt.test.Demo.demo1())" id="mypoint"/>
<aop:advisor advice-ref="mythrow" pointcut-ref="mypoint" />
</aop:config>
<bean id="demo" class="com.bjsxt.test.Demo"></bean>
2)AspectJ方式
只有当切点报异常才能触发异常通知
1.新建类,在类写任意名称的方法
public class MyThrowAdvice{
public void myexception(Exception e1){
System.out.println("执行异常通知"+e1.getMessage());
}
}
2.在Spring配置文件中配置
<aop:aspect>的 ref 属性表示:方法在哪个类中
<aop: xxxx/> 表示什么通知
method: 当触发这个通知时,调用哪个方法
throwing: 异常对象名,必须和通知中方法参数名相同(可以不在通知中声明异常对象)
<bean id="mythrow" class="com.bjsxt.advice.MyThrowAdvice"></bean>
<aop:config>
<aop:aspect ref="mythrow">
<aop:pointcut expression="execution(* com.bjsxt.test.Demo.demo1())" id="mypoint"/>
<aop:after-throwing method="myexception" pointcut-ref="mypoint" throwing="e1"/>
</aop:aspect>
</aop:config>
<bean id="demo" class="com.bjsxt.test.Demo"></bean>
六、配置环绕通知(Schema-based方式)
把前置通知和后置通知都写到一个通知中,组成了环绕通知
新建一个类实现 MethodInterceptor
public class MyArround implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation arg0) throws Throwable {
System.out.println("环绕-前置");
Object result = arg0.proceed();//放行,调用切点方式
System.out.println("环绕-后置");
return result;
}
}
配置 applicationContext.xml
<bean id="myarround" class="com.bjsxt.advice.MyArround"></bean>
<aop:config>
<aop:pointcut expression="execution(* com.bjsxt.test.Demo.demo1())" id="mypoint"/>
<aop:advisor advice-ref="myarround" pointcut-ref="mypoint" />
</aop:config>
<bean id="demo" class="com.bjsxt.test.Demo"></bean>
七、使用注解(基于AspectJ)
Spring不会自动去寻找注解,必须告诉Spring哪些包下的类中可能含有注解
1)引入 xmlns:context 并配置ApplitionContext.xml文件
<context:component-scan base-package="com.bjsxt.advice"></context:component-scan>
2)@Component
在声明类的上方写此注解
相当于<bean/>,如果没有参数则默认id为把类名首字母变小写,相当于<bean id=””/>
还可以@Component(“自定义名称”)
3)具体实现步骤
1.在 spring 配置文件中设置注解在哪些包中
<context:component-scan base-package="com.bjsxt.advice,com.bjsxt.test"></context:component-scan>
2.在 Demo 类中添加@Componet
在方法上添加@Pointcut(“”) 定义切点
@Component
public class Demo {
@Pointcut("execution(* com.bjsxt.test.Demo.demo1())")
public void demo1() throws Exception{
// int i = 5/0;
System.out.println("demo1");
}
}
3.在通知类中配置
@Component
@Aspect 相当于<aop:aspect/> 表示通知方法在当前类中
@Component
@Aspect
public class MyAdvice {
@Before("com.bjsxt.test.Demo.demo1()")
public void mybefore(){
System.out.println("前置");
}
@After("com.bjsxt.test.Demo.demo1()")
public void myafter(){
System.out.println("后置通知");
}
@AfterThrowing("com.bjsxt.test.Demo.demo1()")
public void mythrow(){
System.out.println("异常通知");
}
@Around("com.bjsxt.test.Demo.demo1()")
public Object myarround(ProceedingJoinPoint p) throws Throwable{
System.out.println("环绕-前置");
Object result = p.proceed();
System.out.println("环绕-后置");
return result;
}
}
八、代理设计模式
1)代理设计模式的优点
1.保护真实对象
2.让真实对象指责更明确
3.扩展
2)静态代理设计模式
由代理对象代理所有真实对象的功能,代理类需要自己编写,每个代理的功能都需要单独编写
功能接口
public interface gongneng {
public void chifan();
}
被代理类
public class laoban implements gongneng{
private String name;
public laoban() {
super();
}
public laoban(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void chifan() {
System.out.println("老板吃饭");
}
}
代理类
package com.xijian.pojo;
public class mishu implements gongneng{
private laoban lb = new laoban();
public void chifan() {
System.out.println("记录预约时间");
lb.chifan();
System.out.println("记录结束时间");
}
}
测试方法
package com.xijian.pojo;
public class women {
public static void main(String[] args) {
mishu ms = new mishu();
ms.chifan();
}
}
当接口中功能很多时,代理类也要实现许多功能。
3)动态代理设计模式
为了解决静态代理频繁编写代理功能缺点,就有了动态代理设计模式,分为两种:
1.JDK提供的动态代理(基于反射实现)
2.cglib动态代理(需要导入asm与cglib的jar包,基于字节码实现)
以JDK使用的动态代理为例
真实对象必须实现功能接口,且利用反射机制导致效率不高(与cglib的区别)
功能接口
package com.xijian.pojo1;
public interface Gongneng {
public void chifan();
}
真实对象
package com.xijian.pojo1;
public class LaoBan implements Gongneng {
@Override
public void chifan() {
System.out.println("老板吃饭");
}
}
代理类(实际上是个处理类,真正的代理对象是通过Proxy.newProxyInstance返回的那个对象)
package com.xijian.pojo1;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MiShu implements InvocationHandler{
LaoBan lb = null;
public Object blind(LaoBan lb) {
this.lb = lb;
Gongneng gongneng = (Gongneng)Proxy.newProxyInstance(this.getClass().getClassLoader(),new Class[] {Gongneng.class}, this);
return gongneng;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始");
Object returnValue = method.invoke(lb, args);
System.out.println("结束");
return returnValue;
}
}
代理类必须实现InvocationHandler方法,并通过Proxy.newProxyInstance返回一个实现了 真实对象所实现的接口 的实现类对象
测试类
package com.xijian.pojo1;
public class Women {
public static void main(String[] args) {
LaoBan lb = new LaoBan();
MiShu ms = new MiShu();
Gongneng gongneng = (Gongneng)ms.blind(lb);
gongneng.chifan();
}
}
在通过newProxyInstance返回一个对象后,在调用接口中的某方法的时候,实际上是对处理类的invoke方法的调用。
运行结果:
开始
老板吃饭
结束
————————————————————————————————————————————————————————
在使用SpringAOP时,只要出现Proxy和真实对象转换异常,应在ApplicationContext.xml文件中设置
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
true为使用cglib动态代理,false是使用jdk的动态代理(默认)