什么是AOP?
将分散在各个方法中重复的代码提取出来,然后在程序编译或运行时,再将这些提取出来的代码应用到需要执行的地方
AOP术语
点击此处学习AOP术语
在切面覆盖下的类里面,任何对象方法的调用都被叫做连接点。
切面 只是一个类,但是这个类只有在Spring容器中被配置才能被识别为切面
切入点就是 需要处理的连接点
通知增强处理就是 切入点需要执行的程序代码,就是切面类中的某个方法
目标对象就是 执行了切面类中方法的一个类。
代理是 在“动态的AOP”方法中,被动态创建的虚拟对象。
织入也就是 生成代理对象的过程
下面来了解一下spring 中两种代理模式。
JDK动态代理:
流程图:
项目架构:
代码:
package com.csdn2.AOP;
public interface UserDao {
public void add();
public void delete();
}
package com.csdn2.AOP;
public class UserDaoImplement implements UserDao{
@Override
public void add() {
// TODO Auto-generated method stub
System.out.println("com.csdn2.AOP.UserDaoImplement:添加方法");
}
@Override
public void delete() {
// TODO Auto-generated method stub
System.out.println("com.csdn2.AOP.UserDaoImplement:删除方法");
}
}
package com.csdn2.AOP;
public class Aspect {
public void check() {
System.out.println("com.csdn2.AOP.Aspect:模拟检查操作......");
}
public void log() {
System.out.println("com.csdn2.AOP.Aspect:模拟记录操作......");
}
}
package com.csdn2.AOP;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JdkProxy implements InvocationHandler{
//声明目标对象
UserDao userDao;
public Object createProxy(UserDao userDao) {
this.userDao=userDao;
//1、类加载器
ClassLoader classLoader=JdkProxy.class.getClassLoader();
//2、被代理对象所有接口
Class[] clazz=userDao.getClass().getInterfaces();
//3、使用代理类,进行增强,返回的是代理后的对象
return Proxy.newProxyInstance(classLoader, clazz, this);
}
//所有动态代理类的方法调用,都会交由invoke()方法去处理
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
//1、声明切面
Aspect aspect=new Aspect();
//2、前增强
aspect.check();
//3、再在目标类上调用方法,并传入参数
Object obj=method.invoke(userDao, args);
//3、后增强
aspect.log();
return obj;
}
}
package com.csdn2.AOP;
public class JdkTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建代理对象
JdkProxy jdkProxy=new JdkProxy();
//创建目标对象
UserDao userDao=new UserDaoImplement();
//从代理对象中获取增强后的目标对象
UserDao userDao2=(UserDao)jdkProxy.createProxy(userDao);
//执行方法
userDao2.add();
System.out.println();
userDao2.delete();
}
}
运行结果
CDGLIB代理:
Code Generation Library 一个高效能的开源的代码生成包,采用字节码技术,对目标类生成一个子类,并对子类进行增强。spring的核心包中(spring-core)中以及集成了CGLIB所需要的包,开发中不需要进行另外的导包操作,并且基于CGLIB不需要像 JDK代理方式那样一定要求:使用动态代理的对象必须实现了一个或者几个接口。
流程图:
项目结构:
代码:
package com.csdn2.AOP.CGLIB;
public class UserDao {
public void add() {
System.out.println("执行添加方法");
}
public void delete() {
System.out.println("执行删除方法");
}
}
package com.csdn2.AOP.CGLIB;
public class aspect {
public void log() {
System.out.println("正在执行切面中的日志记录.....");
}
public void check() {
System.out.println("正在执行切面中的检查方法......");
}
}
package com.csdn2.AOP.CGLIB;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
public class CGLIBproxy implements MethodInterceptor{
public Object createProxy(Object object) {
Enhancer enhancer=new Enhancer();
enhancer.setSuperclass(object.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
// TODO Auto-generated method stub
aspect aspect=new aspect();
aspect.check();
Object object=arg3.invokeSuper(arg0,arg2);
aspect.log();
return object;
}
}
package com.csdn2.AOP.CGLIB;
public class test {
public static void main(String[] args) {
// TODO Auto-generated method stub
CGLIBproxy cgliBproxy=new CGLIBproxy();
UserDao userDao=new UserDao();
UserDao userDao2=(UserDao)cgliBproxy.createProxy(userDao);
userDao2.add();
System.out.println();
userDao2.delete();
}
}
运行结果:
目标类中的方法别成功调用并增强了,这种没有实现接口的调用方式,就是CGLIB方法。
基于代理类的AOP代理
在Spring中 提供了ProxyFactoryBean创建AOP代理,
ProxyFactoryBean的常用属性:
属性名称 | 描述 |
---|---|
target | 代理的目标对象 |
proxyInterface | 代理所需要的实现的接口,可以是多个接口,可以使用一下格式赋值< list >< value >< /value >…< /list > |
interceptorNames | 需要植入的目标对象的Advice |
proxyTargertClass | 是否对类进行代理(而不是对接口进行代理)。设置为true时,使用cglib代理。 |
singleton | 返回的代理对象是否单例,默认是单例 |
optimize | 当设置为true时 ,强制使用cglib代理,对于singleton的代理,我们推荐使用cglib代理,对于其他类型的代理,最好使用JDK代理,原因是cglib创建代理时速度慢,而创建出的代理对象运行效率较高,而使用JDK代理的表现正好相反。 |
流程图:
引包
spring-aop-X.X.X 在Spring包中可以找到。
aopalliance-1.0.jar下载地址,截止到博主写这篇文章开始,这个jar包的最新版还是1.0版本的,5年未更新过了
项目结构:
代码:
package com.csdn2.AOP.Factorybean;
public interface UserDao {
public void add();
public void delete();
}
package com.csdn2.AOP.Factorybean;
public class UserDaoImplement implements UserDao{
@Override
public void add() {
// TODO Auto-generated method stub
System.out.println("com.csdn2.AOP.Factorybean.UserDaoImplement:添加方法");
}
@Override
public void delete() {
// TODO Auto-generated method stub
System.out.println("com.csdn2.AOP.Factorybean.UserDaoImplement:删除方法");
}
}
package com.csdn2.AOP.Factorybean;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class aspect implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation arg0) throws Throwable {
// TODO Auto-generated method stub
check();
Object object=arg0.proceed();
log();
return null;
}
public void check() {
System.out.println("执行切面中的检查方法.....");
}
public void log() {
System.out.println("执行切面中的日志记录方法.....");
}
}
<?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="userDao" class="com.csdn2.AOP.Factorybean.UserDaoImplement"></bean>
<!-- 切面类 -->
<bean id="aspect" class="com.csdn2.AOP.Factorybean.aspect"></bean>
<!-- 使用spring代理工厂定义一个代理对象 -->
<bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 指定代理实现的接口 -->
<property name="proxyInterfaces" value="com.csdn2.AOP.Factorybean.UserDao"></property>
<!-- 指定目标对象 -->
<property name="target" ref="userDao"></property>
<!-- 指定切面,植入环绕通知 -->
<property name="interceptorNames" value="aspect"></property>
<!-- 指定代理方法,true:CGLIB,false:JDK -->
<property name="proxyTargetClass" value="true"></property>
</bean>
</beans>
package com.csdn2.AOP.Factorybean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class test {
public static void main(String[] args) {
// TODO Auto-generated method stub
// String xmlPath="com/casdn2/AOP/Factorybean/ac.xml";
ApplicationContext applicationContext=
new ClassPathXmlApplicationContext("ac.xml");
UserDao userDao=(UserDao)applicationContext.getBean("userDaoProxy");
userDao.add();
System.out.println();
userDao.delete();
}
}
执行结果:
AspectJ开发:
新版本 的Spring框架,建议使用AspectJ来开发AOP。
使用AspectJ实现AOP有两种方式:一种的基于XML的声明式AspectJ、另一种是基于注解的声明式AspectJ。
基于XML的声明式AspectJ:
切面、切入点、通知都通过XML文件进行定义。
aop:config元素及其子元素:
< aop:aspect >元素的属性及其描述:
属性名称 | 描述 |
---|---|
id | 用于定义该切面的唯一标识 |
ref | 用于引用普通的Spring Bean |
< aop:pointcut >元素的属性及其描述:
属性名称 | 描述 |
---|---|
id | 用于指定切入点的唯一标识 |
expression | 用于指定切入点关联的切入点表达式 |
通知的常用属性及其描述:
属性名称 | 描述 |
---|---|
pointcut | 该属性用于指定一个切入点,Spring将在匹配该表达式的连接点时织入该通知 |
pointcut-ref | 该属性用于指定一个已经存在的切入点名称,如配置该代码中 |
myPointCut | 通常pointcut和pointcut-ref两个属性只需要使用其中之一 |
method | 该属性指定一个方法名,指定将切面Bean中的该方法转换为增强 |
throwing | 该属性只对 元素有效,它用与指定一个形象名,异常通知方法可以通过该形参访问目标方法所抛出的异常 |
returning | 该属性只对元素有效,它用于指定一个形参名,后置通知方法可以该形参访问目标方法的返回值 |
流程图:
项目结构:
代码:
package com.csdn2.AOP.AspectJ.Xml;
public interface UserDao {
public void add();
public void delete();
}
package com.csdn2.AOP.AspectJ.Xml;
public class UserDaoImplement implements UserDao{
@Override
public void add() {
// TODO Auto-generated method stub
System.out.println("com.csdn2.AOP.AspectJ.Xml.UserDaoImplement:添加方法");
}
@Override
public void delete() {
// TODO Auto-generated method stub
System.out.println("com.csdn2.AOP.AspectJ.Xml.UserDaoImplement:删除方法");
}
}
package com.csdn2.AOP.AspectJ.Xml;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class aspect {
public void myBefore(JoinPoint joinPoint) {
System.out.print("前置通知:模拟执行权限检查......");
System.out.print(",目标类是:"+joinPoint.getClass());
System.out.println(",被植入增强处理的目标方法为:"+joinPoint.getSignature().getName());
}
public void myAfterReturning(JoinPoint joinPoint) {
System.out.print("后置通知:模拟记录日志....");
System.out.println("被植入增强处理的目标方法为:"+joinPoint.getSignature().getName());
}
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
System.out.println("环绕开始:执行目标方法之前,模拟开启事务.....");
Object object=proceedingJoinPoint.proceed();
System.out.println("环绕结束:执行目标方法之后,模拟关闭事务.....");
return object;
}
public void myAfterThrowing(JoinPoint joinPoint ,Throwable e) {
System.out.println("异常处理:"+"出错了"+e.getMessage());
}
public void myAfter() {
System.out.println("最终通知:模拟方法结束后的释放资源");
}
}
package com.csdn2.AOP.AspectJ.Xml;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class test {
public static void main(String[] args) {
// TODO Auto-generated method stub
ApplicationContext applicationContext=
new ClassPathXmlApplicationContext("com/csdn2/AOP/AspectJ/Xml/ac.xml");
UserDao userDao=
(UserDao)applicationContext.getBean("userDao");
userDao.add();
}
}
<?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="userDao" class="com.csdn2.AOP.AspectJ.Xml.UserDaoImplement"></bean>
<!-- 切面 -->
<bean id="aspect" class="com.csdn2.AOP.AspectJ.Xml.aspect"></bean>
<!-- aop编程 -->
<aop:config>
<aop:aspect ref="aspect">
<aop:pointcut id="mypointcut" expression="execution(* com.csdn2.AOP.AspectJ.Xml.*.*(..))" />
<aop:before method="myBefore" pointcut-ref="mypointcut"/>
<aop:after-returning method="myAfterReturning" pointcut-ref="mypointcut" returning="returnVal"/>
<aop:around method="myAround" pointcut-ref="mypointcut"/>
<aop:after-throwing method="myAfterThrowing" pointcut-ref="mypointcut" throwing="e"/>
<aop:after method="myAfter" pointcut-ref="mypointcut"/>
</aop:aspect>
</aop:config>
</beans>
运行结果:
基于注解的声明式AspectJ*:
虽然与基于代理类的AOP实现相比,基于XML的声明式AspectJ要便捷很多了,但是任然需要在XML文件中配置大量的代码信息。为此AspectJ为AOP提供了一套注解,用以取代Spring配置文件中为实现AOP功能所配置的臃肿代码。
流程图同上
项目结构:
代码:
package com.csdn2.AOP.AspectJ.annotation;
public interface UserDao {
public void add();
public void delete();
}
package com.csdn2.AOP.AspectJ.annotation;
import org.springframework.stereotype.Repository;
@Repository("userDao")
public class UserDaoImplement implements UserDao{
@Override
public void add() {
// TODO Auto-generated method stub
System.out.println("com.csdn2.AOP.AspectJ:添加方法");
}
@Override
public void delete() {
// TODO Auto-generated method stub
System.out.println("com.csdn2.AOP.AspectJ.Xml:删除方法");
}
}
package com.csdn2.AOP.AspectJ.annotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class aspect {
@Pointcut("execution(* com.csdn2.AOP.AspectJ.annotation.*.*(..))")
private void myPointCut() {}//使用一个返回值为void ,方法体为空 的方法来命名切入点
@Before("myPointCut()")
public void myBefore(JoinPoint joinPoint) {
System.out.print("前置通知:模拟执行权限检查......");
System.out.print(",目标类是:"+joinPoint.getClass());
System.out.println(",被植入增强处理的目标方法为:"+joinPoint.getSignature().getName());
}
@AfterReturning("myPointCut()")
public void myAfterReturning(JoinPoint joinPoint) {
System.out.print("后置通知:模拟记录日志....");
System.out.println("被植入增强处理的目标方法为:"+joinPoint.getSignature().getName());
}
@Around("myPointCut()")
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
System.out.println("环绕开始:执行目标方法之前,模拟开启事务.....");
Object object=proceedingJoinPoint.proceed();
System.out.println("环绕结束:执行目标方法之后,模拟关闭事务.....");
return object;
}
@AfterThrowing(value="myPointCut()" ,throwing="e")
public void myAfterThrowing(JoinPoint joinPoint ,Throwable e) {
System.out.println("异常处理:"+"出错了"+e.getMessage());
}
@After("myPointCut()")
public void myAfter() {
System.out.println("最终通知:模拟方法结束后的释放资源");
}
}
<?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"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 指定需要扫描的包,使注解生效 -->
<context:component-scan base-package="com.csdn2.AOP.AspectJ.annotation"/>
<!-- 启动基于注解的声明式AspectJ支持 -->
<aop:aspectj-autoproxy />
</beans>
package com.csdn2.AOP.AspectJ.annotation;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class test {
public static void main(String[] args) {
// TODO Auto-generated method stub
ApplicationContext applicationContext=
new ClassPathXmlApplicationContext("com/csdn2/AOP/AspectJ/annotation/ac.xml");
UserDao userDao=
(UserDao)applicationContext.getBean("userDao");
userDao.add();
}
}
运行结果:
目标方法前后通知的执行顺序发生了变化: