Spring AOP介绍:实现方式动态代理
AOP编程:AOP是Spring框架面向切面的编程思想,AOP采用一种称为“横切”的技术,将涉及多业务流程的通用功能抽取并单独封装,形成独立的切面,在合适的时机将这些切面横向切入到业务流程指定的位置中,即在不改变原有类代码的情况下,拦截目标代码(字段或方法),在运行期使用代理方式对目标类植入增强代码进行增强,添加业务所需的操作。
主要应用:日志记录,性能统计,安全控制,事务处理,异常处理等等。
注:Spring AOP默认使用AOP代理的标准JDK动态代理。这使得任何接口(或接口集)都可以被代理。
Spring AOP也可以使用CGLIB代理。这是代理类而不是接口所必需的。
默认情况下,如果业务对象未实现接口,则使用CGLIB。
Aspect: Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。切入点和advice的结合。
Joint point:表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。指那些可能被拦截的点(方法);
Pointcut:表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。(被增强的连接点)
Advice:Advice 定义了在 pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
weaving:把增强advice应用到目标target来创建代理对象proxy的过程
通知类型:
- Before advice: Advice that runs before a join point but that does not have the ability to prevent execution flow proceeding to the join point (unless it throws an exception).
- After returning advice: Advice to be run after a join point completes normally (for example, if a method returns without throwing an exception).
- After throwing advice: Advice to be executed if a method exits by throwing an exception.
- After (finally) advice: Advice to be executed regardless of the means by which a join point exits (normal or exceptional return).
- Around advice: Advice that surrounds a join point such as a method invocation. This is the most powerful kind of advice. Around advice can perform custom behavior before and after the method invocation. It is also responsible for choosing whether to proceed to the join point or to shortcut the advised method execution by returning its own return value or throwing an exception.
- 前置通知:在连接点之前运行但无法阻止执行流程进入连接点的建议(除非它抛出异常)。
- 后置通知:在连接点正常完成后运行的建议(例如,如果方法返回而不抛出异常)。
- 异常抛出通知:如果方法通过抛出异常退出,则执行建议。
- 后置通知(finally):无论连接点退出的方式(正常或异常返回),都要执行建议。
- 环绕通知:围绕连接点的建议,例如方法调用。这是最有力的建议。around通知可以在方法调用之前和之后执行自定义行为。它还负责选择是继续加入点还是通过返回自己的返回值或抛出异常来快速建议的方法执行。
Spring jdk代理
(1)手动代理(目标类有接口)
实现方式:使用jdk代理方法Proxy.newProxyInstance(loader, interfaces, handler),loader:ClassLoader,类加载器(ClassName.class.getClassLoader()),interfaces:类实现的接口(ClassName.class.getInterfaces()), handler:实现InvocationHandler接口的类实例,编写invoke()函数,实现如下
class DynamicProxyHeadlerInvocationHandler implements InvocationHandler{
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
aspect.beforeClass();//前置消息
Object obj=method.invoke(target, args);
aspect.afterClass();//后置消息
return obj;
}
}
通知类StudentAspect.java
package com.jty.proxy;
import org.aopalliance.aop.Advice;
public class StudentAspect implements Advice{
public void beforeClass() {
System.out.println("==============前置通知==============");
System.out.println("课前休息15分钟~");
}
public void afterClass() {
System.out.println("==============后置通知==============");
System.out.println("课后前休息15分钟~");
}
}
目标接口Student.java
package com.jty.proxy;
public interface Student {
public void havingClass();
}
实现类StudentImpl.java
package com.jty.proxy;
import com.jty.testSpring.Grade;
public class StudentImpl implements Student{
private String name;//姓名
private int ages;//年龄
private String address;//地址
private double cost;//零花钱
public StudentImpl() {
super();
this.name="用户1";
this.ages=20;
this.address="江苏省南京市";
this.cost=20.00;
}
public StudentImpl(String name, Integer ages, String address, Double cost, Grade grade) {
super();
this.name = name;
this.ages = ages;
this.address = address;
this.cost = cost;
}
public void havingClass() {
System.out.println("正在上课。。。。。。。。。。");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAges() {
return ages;
}
public void setAges(int ages) {
this.ages = ages;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public double getCost() {
return cost;
}
public void setCost(double cost) {
this.cost = cost;
}
@Override
public String toString() {
return "Student [name=" + name + ", ages=" + ages + ", address=" + address + ", cost=" + cost + "]";
}
}
代理类StudentProxyWithJdk.java
package com.jty.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class StudentProxyWithJdk{
private StudentAspect aspect;
private Object target;
public StudentProxyWithJdk() {
super();
}
public StudentProxyWithJdk(StudentAspect aspect, Object target) {
super();
this.aspect =aspect;
//类型转换为目标类型
this.target = (target.getClass().cast(target)) ;
System.out.println(target);
}
//匿名内部类
public Student createStudentProxy() {
return (Student) Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
aspect.beforeClass();
Object obj=method.invoke(target, args);
aspect.afterClass();
return obj;
}
});
}
//命名内部类
/* class DynamicProxyHeadlerInvocationHandler implements InvocationHandler{
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
aspect.beforeClass();
Object obj=method.invoke(target, args);
aspect.afterClass();
return obj;
}
}
public Student createStudentProxy() {
return (Student) Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new DynamicProxyHeadlerInvocationHandler());
}*/
}
输出:
正在上课。。。。。。。。。。
==============前置通知==============
课前休息15分钟~
正在上课。。。。。。。。。。
==============后置通知==============
课后前休息15分钟~
cglib字节码增强(spring核心包core包含了相关jar包)
通过继承目标类实现子类的方式实现代理,核心对象 Enhancer enhancer=new Enhancer()
,通过调用enhancer的方法创建子类实现代理,步骤如下:
- 定义目标类实例;
- 定义通知类实例;
- 定义Enhancer实例;
- 设置父类为目标类Enhancer.setSupperclass();
- 编写Enhancer.setCallback(intercepter) 方法,增强目标为函数,intercepter类型为MethodIntercepter;
- 生成子类对象 enhancer.create();
通知类StudentAspect.java
package com.jty.proxy;
import org.aopalliance.aop.Advice;
public class StudentAspect implements Advice{
public void beforeClass() {
System.out.println("==============前置通知==============");
System.out.println("课前休息15分钟~");
}
public void afterClass() {
System.out.println("==============后置通知==============");
System.out.println("课后前休息15分钟~");
}
}
目标类StudentCglib.java
package com.jty.proxy;
import com.jty.testSpring.Grade;
public class StudentCglib {
private String name;//姓名
private int ages;//年龄
private String address;//地址
private double cost;//零花钱
public StudentCglib() {
super();
this.name="用户1";
this.ages=20;
this.address="江苏省南京市";
this.cost=20.00;
}
public StudentCglib(String name, Integer ages, String address, Double cost, Grade grade) {
super();
this.name = name;
this.ages = ages;
this.address = address;
this.cost = cost;
}
public void havingClass() {
System.out.println("正在上课。。。。。。。。。。");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAges() {
return ages;
}
public void setAges(int ages) {
this.ages = ages;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public double getCost() {
return cost;
}
public void setCost(double cost) {
this.cost = cost;
}
@Override
public String toString() {
return "Student [name=" + name + ", ages=" + ages + ", address=" + address + ", cost=" + cost + "]";
}
}
cglib代理类StudentProxyWithCglib.java
package com.jty.proxy;
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
public class StudentProxyWithCglib {
private StudentAspect studentAspect;
private StudentCglib studentCglib;
public StudentProxyWithCglib() {
super();
}
public StudentProxyWithCglib(StudentAspect studentAspect, StudentCglib studentCglib) {
super();
this.studentAspect = studentAspect;
this.studentCglib = studentCglib;
}
public StudentCglib createStudentProxy() {
//使用cglib增强核心类
Enhancer enhancer=new Enhancer();
//设置父类为目标类
enhancer.setSuperclass(studentCglib.getClass());
//拦截增强方法,对方法进行增强
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
studentAspect.beforeClass();
Object obj=method.invoke(studentCglib, args);
studentAspect.afterClass();
return obj;
}
});
return (StudentCglib) enhancer.create();
}
}
输出:
正在上课。。。。。。。。。。
==============前置通知==============
课前休息15分钟~
正在上课。。。。。。。。。。
==============后置通知==============
课后前休息15分钟~
spring半自动代理
spring代理通知介绍,实现aop联盟规定的通知接口
在切面类中要实现(aop联盟)通知接口,也就是aop联盟提供的规范
环绕通知(Round advice)
- 实现环绕通知需要实现org.aopalliance.intercept.MethodInterceptor接口。
- 前置通知(Before advice)
实现前置通知需要实现org.springframework.aop.MethodBeforeAdvice接口。 - 后通知(After Returning advice)
实现返回后通知需要实现org.springframework.aop.AfterReturningAdvice接口。 - 异常通知(Throws advice)
当连接点抛出异常时,异常通知被调用。实现异常通知需要实现org.springframework.aop.ThrowsAdvice接口,
该接口不包含任何方法,但在实现该接口时必须实现如下形式的方法:
afterThrowing([Method], [args], [target], Throwable subclass)
可以实现一个或多个这样的方法。在这些方法中,只有第四个参数是必需的,前三个参数可选。
= 引入通知(Introduction advice)
引入通知是一种特殊的通知,它能将新的成员变量、成员方法引入到目标类中。
它不能作用于任何切入点,因为它只作用于类层次,而不是方法层次。
实现引入通知需要实现IntroductionAdvisor和IntroductionInterceptor接口。
引入通知不能调用proceed方法。Advisor必须针对每个实例,并且是有状态的。
引入通知的效果类似于设计模式中的访问者模式(Visitor Pattern)。
接口student.java
package com.jty.springfactorybeanproxy;
public interface Student {
public void havingClass();
}
目标类StudentImpl.java
package com.jty.springfactorybeanproxy;
import com.jty.testSpring.Grade;
public class StudentImpl implements Student{
private String name;//姓名
private int ages;//年龄
private String address;//地址
private double cost;//零花钱
public StudentImpl() {
super();
this.name="用户1";
this.ages=20;
this.address="江苏省南京市";
this.cost=20.00;
}
public StudentImpl(String name, Integer ages, String address, Double cost, Grade grade) {
super();
this.name = name;
this.ages = ages;
this.address = address;
this.cost = cost;
}
public void havingClass() {
System.out.println(this.toString());
System.out.println("正在上课。。。。。。。。。。");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAges() {
return ages;
}
public void setAges(int ages) {
this.ages = ages;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public double getCost() {
return cost;
}
public void setCost(double cost) {
this.cost = cost;
}
@Override
public String toString() {
return "Student [name=" + name + ", ages=" + ages + ", address=" + address + ", cost=" + cost + "]";
}
}
代理类StudentAspect.java
package com.jty.springfactorybeanproxy;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class StudentAspect implements MethodInterceptor{
//实现环绕通知
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("==============前置通知==============");
System.out.println("课前休息15分钟~");
Object obj=invocation.proceed();
System.out.println("==============后置通知==============");
System.out.println("课后休息15分钟~");
return obj;
}
}
spring配置文件applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="target" class="com.jty.springfactorybeanproxy.StudentImpl"></bean>
<bean id="aspect" class="com.jty.springfactorybeanproxy.StudentAspect"></bean>
<bean id="studentProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 创建代理类
ProxyFactoryBean:用于创建代理的工厂bean
interfaces:代理目标类的接口 ,单个用value,多个用<array><value></value></array>
target:目标类
interceptorNames:切面类的名称,(拦截器的名称)
optimize:true,强制使用cglib,底层使用cglib字节码增强,false:若有接口使用jdk动态代理,若无则使用cglib字节码增强
-->
<property name="interfaces" value="com.jty.springfactorybeanproxy.Student"></property>
<property name="target" ref="target"></property>
<property name="interceptorNames" value="aspect"></property>
<property name="optimize" value="true"></property>
</bean>
</beans>
测试
package com.jty.testSpring;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.jty.springfactorybeanproxy.Student;
public class TestSpringFactoryBeanProxy {
public static void main(String[] args) {
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("com/jty/springfactorybeanproxy/ApplicationContext.xml");
Student studentProxy=(Student) applicationContext.getBean("studentProxy");
studentProxy.havingClass();
}
}
输出:
==============前置通知==============
课前休息15分钟~
Student [name=用户1, ages=20, address=江苏省南京市, cost=20.0]
正在上课。。。。。。。。。。
==============后置通知==============
课后休息15分钟~
springAop代理
xml文件总需要引入aop约束applicationContext.xml
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="advice" class="com.jty.springAopProxy.StudentAspect"></bean>
<!-- spring-aop编程
引入aop命名空间:xmlns:aop="http://www.springframework.org/schema/aop"
<aop:config>配置aop编程
<aop:pointcut>:切入点,要增强的方法
expression:切入点表达式
execution(修饰符(public|*|省略) 返回值(objectType|*) 包(*|包名|省略|..(子孙包,一般放在最后)).类.方法(参数(跟返回值一样|无参)) throws 异常),
可用通配符‘*’ 代替其中一些部分,如方法名,包名等
可用..代替本身及其包含的内容,包括子孙包
<aop:advisor>:一个特殊的切面,一个通知和一个切入点
-->
<aop:config>
<aop:pointcut expression="execution(* com.jty.springAopProxy.StudentImpl.*(..))" id="pointCut"/>
<aop:advisor advice-ref="advice" pointcut-ref="pointCut"/>
</aop:config>
<bean id="student" class="com.jty.springAopProxy.StudentImpl"></bean>
</beans>
测试
package com.jty.testSpring;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.jty.springAopProxy.Student;
public class TestSpringAopProxy {
public static void main(String[] args) {
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("com/jty/springAopProxy/ApplicationContext.xml");
Student studentProxy=(Student) applicationContext.getBean("student");
studentProxy.havingClass();
}
}
输出:
==============前置通知==============
课前休息15分钟~
Student [name=用户1, ages=20, address=江苏省南京市, cost=20.0]
正在上课。。。。。。。。。。
==============后置通知==============
课后休息15分钟
Aspectj代理
Aspectj的通知类型:
-
前通知(Before Advice) ;当到达一个连接点但是在程序进程运行之前执行。
-
后通知(After Advice) 当特定连接点处的程序进程执行之后运行。相对的就有三种后通知:返回后通知(after returning)、抛出异常后通知(after throwing)和清楚的后通知(after),
-
所谓清楚后通知就是指无论是正常还是异常都执行的后通知,就像Java中的finally语句。
-
环绕通知(Around Advice) 在连接点到达后,显示的控制程序进程是否执行,方法执行前后分别执行,能实现其他通知所做的所有事情,必须手动执行目标方法。
try{
//before advice
excute target mothed...
//afterReturning
}catch(){
//after throwing
}finally{
//after
}
步骤:
- 导入jar包
aop联盟规范:srping-aop已整合
springAop实现:spring-aop
aspect规范:aspectjweaver
spring aspect实现:spring-aspects
Student.java接口
package com.jty.aspectjProxy;
public interface Student {
public void havingClass();
}
StudentImpl.java
package com.jty.aspectjProxy;
import com.jty.testSpring.Grade;
public class StudentImpl implements Student{
private String name;//姓名
private int ages;//年龄
private String address;//地址
private double cost;//零花钱
public StudentImpl() {
super();
this.name="用户1";
this.ages=20;
this.address="江苏省南京市";
this.cost=20.00;
}
public StudentImpl(String name, Integer ages, String address, Double cost, Grade grade) {
super();
this.name = name;
this.ages = ages;
this.address = address;
this.cost = cost;
}
public void havingClass() {
System.out.println(this.toString());
System.out.println("正在上课。。。。。。。。。。");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAges() {
return ages;
}
public void setAges(int ages) {
this.ages = ages;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public double getCost() {
return cost;
}
public void setCost(double cost) {
this.cost = cost;
}
@Override
public String toString() {
return "Student [name=" + name + ", ages=" + ages + ", address=" + address + ", cost=" + cost + "]";
}
}
StudentAspectj.java
package com.jty.aspectjProxy;
import org.aspectj.lang.ProceedingJoinPoint;
public class StudentAspect {
//前置通知
public void before() {
System.out.println("前置通知");
System.out.println("课前休息10分钟--------");
}
public void after() {
System.out.println("后置通知");
System.out.println("课后休息10分钟--------");
}
public void afterReturning() {
System.out.println("返回后通知");
System.out.println("下课时间到-------");
}
public void afterThrowing() {
System.out.println("异常通知");
System.out.println("中途下课--------");
}
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕通知");
Object retVal=null;
try {
this.before();
retVal = pjp.proceed();
this.afterReturning();
}catch(Exception e) {
this.afterThrowing();
}finally {
this.after();
}
return retVal;
}
}
applicationContext.xml
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="advice" class="com.jty.aspectjProxy.StudentAspect"></bean>
<bean id="student" class="com.jty.aspectjProxy.StudentImpl"></bean>
<!--(1) <aop:pointcut expression="execution(* com.jty.aspectjProxy.StudentImpl.*(..))" id="myPointcut"/>
定义切入点,即目标方法
(2)
<aop:aspect id="myAspect" ref="aBean">
...
</aop:aspect>
定义切面
(3)<aop:before method="before" pointcut-ref="" pointcut=""/>
pointcut-ref:切入点的引用
pointcut:切入点表达式,单独适用于本通知-->
<aop:config>
<!--目标方法的切入点表达式-->
<aop:pointcut expression="execution(* com.jty.aspectjProxy.StudentImpl.*(..))" id="myPointcut"/>
<!--将通知应用于目标方法-->
<aop:aspect id="myAspectj" ref="advice">
<!-- <aop:before method="before" pointcut-ref="myPointcut"/> -->
<!-- <aop:after method="after" pointcut-ref="myPointcut"/> -->
<!-- <aop:after-returning method="afterReturning" pointcut-ref="myPointcut"/> -->
<!-- <aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut"/> -->
<aop:around method="around" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
</beans>
TestAspectj.java
package com.jty.testSpring;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.jty.aspectjProxy.Student;
public class TestAspectj {
public static void main(String[] args) {
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("com/jty/aspectjProxy/ApplicationContext.xml");
Student studentProxy=(Student) applicationContext.getBean("student");
studentProxy.havingClass();
}
}
输出:
环绕通知
前置通知
课前休息10分钟--------
Student [name=用户1, ages=20, address=江苏省南京市, cost=20.0]
正在上课。。。。。。。。。。
返回后通知
下课时间到-------
后置通知
课后休息10分钟--------