Spring----AOP原理、操作、动态代理设计模式

一、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的动态代理(默认)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值