Spring之AOP实现讲解

    科技2026-01-14  11

    目录

    1 使用AOP的4种方式

    1.1 基于代理的AOP实现

    1.2 aspectj静态代理实现AOP

    1.3 jdk动态代理实现AOP

    1.4 cglib动态代理实现AOP

    2 Spring中AOP实现

    2.1 JDK动态代理

    2.2 Cglib动态代理

    3 Spring AOP实例

    3.1 基于XML配置方式

    3.2 基于注解方式


    1 使用AOP的4种方式

    1.1 基于代理的AOP实现

    经典的基于代理的AOP实现,用的是一个helloworld为例:

    public interface HelloWorld { void printHelloWorld(); void doPrint(); }

    (2)定义两个接口实现类。

    public class HelloWorldImpl1 implements HelloWorld { public void printHelloWorld() { System.out.println("------11111------按下HelloWorld1.printHelloWorld()-----11111111-------"); } public void doPrint() { System.out.println("------1111111------打印HelloWorldImpl1-----1111111------"); return ; } } public class HelloWorldImpl2 implements HelloWorld { public void printHelloWorld() { System.out.println("------222222------按下HelloWorld2.printHelloWorld()------2222222------"); } public void doPrint() { System.out.println("-------22222-----打印HelloWorldImpl2------22222-----"); return ; } }

    (3)HelloWorld的两个实现类关注的是业务逻辑,但在此之外还需要其他的功能逻辑等,如打印时间、打印日志等等。这里开始就需要AOP替“HelloWorldImpl”完成!解耦!首先需要一个TimeHandler类。因为一个是切入点前执行、一个是切入点之后执行,所以实现对应接口。 横切关注点,这里是打印时间:

    public class TimeHandler implements MethodBeforeAdvice, AfterReturningAdvice { public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("代理----前----CurrentTime = " + System.currentTimeMillis()); } public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("代理----后----CurrentTime = " + System.currentTimeMillis()); } }

    (3)最关键的来了,Spring核心配置文件application.xml配置AOP

    <?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 http://www.springframework.org/schema/beans/spring-beans.xsd "> <!-- 定义被代理者 --> <bean id="h1" class="com.lym.aopTest.HelloWorldImpl1"></bean> <bean id="h2" class="com.lym.aopTest.HelloWorldImpl2"></bean> <!-- 定义通知内容,也就是切入点执行前后需要做的事情 --> <bean id="timeHandler" class="com.lym.aopTest.TimeHandler"></bean> <!-- 定义切入点位置,这里定义到了doPrint方法上 --> <bean id="timePointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut"> <property name="pattern" value=".*doPrint"></property> </bean> <!-- 使切入点与通知相关联,完成切面配置 --> <bean id="timeHandlerAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"> <property name="advice" ref="timeHandler"></property> <property name="pointcut" ref="timePointcut"></property> </bean> <!-- 设置代理 --> <bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <!-- 代理的对象,有打印时间能力 --> <property name="target" ref="h1"></property> <!-- 使用切面 --> <property name="interceptorNames" value="timeHandlerAdvisor"></property> <!-- 代理接口,hw接口 --> <property name="proxyInterfaces" value="com.lym.aopTest.HelloWorld"></property> </bean> <!-- 设置代理 --> <bean id="proxy2" class="org.springframework.aop.framework.ProxyFactoryBean"> <!-- 代理的对象,有打印时间能力 --> <property name="target" ref="h2"></property> <!-- 使用切面 --> <property name="interceptorNames" value="timeHandlerAdvisor"></property> <!-- 代理接口,hw接口 --> <property name="proxyInterfaces" value="com.lym.aopTest.HelloWorld"></property> </bean> <!--<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>--> </beans>

    (5)测试类,Test,其中,通过AOP代理的方式执行h1、h2,其中doPrint()方法会把执行前、执行后的操作执行,实现了AOP的效果!

    public class Test { public static void main(String[] args){ //@SuppressWarnings("resource") //如果是web项目,则使用以下代码加载配置文件,如果是一般的Java项目,则使用注释的方式 ApplicationContext appCtx = new ClassPathXmlApplicationContext("conf/application.xml"); //ApplicationContext appCtx = new FileSystemXmlApplicationContext("conf/application.xml"); HelloWorld hw1 = (HelloWorld) appCtx.getBean("proxy"); HelloWorld hw2 = (HelloWorld) appCtx.getBean("proxy2"); hw1.printHelloWorld(); System.out.println(); hw1.doPrint(); System.out.println(); hw2.printHelloWorld(); System.out.println(); hw2.doPrint(); } }

    打印结果如下,可以看到,配置在h1、h2的doPrint()前后打印时间的方法都执行了:

    1.2 aspectj静态代理实现AOP

    利用aspectj实现AOP功能,需要安装eclipse的aspectj插件(编译*.aj文件,jdk编译不了*.aj文件),依赖aspectj的jar包。

    其中要用到aspectj-maven-plugin插件。

    Hello.java

    package com.java.proxy.aspectj; public class Hello { public void hello(String name) { System.out.println(name+",hello!"); } }

    HelloWorld.java

    package com.java.proxy.aspectj; public aspect HelloWorld { /** * 第一个*号是指返回值不限,第二个*号是指方法名不限 * 括号只是任意个数类型不限的形参 */ before() : call(* com.java.proxy.aspectj.*.*(..)) { System.out.println("hello前的检查,哈哈"); } after() : call(* com.java.proxy.aspectj.*.*(..)) { System.out.println("hello后的检查,哈哈"); } }

    测试类

    package com.java.proxy.aspectj; public class Test { public static void main(String[] args) { Hello hello = new Hello(); hello.hello("张三"); } }

    1.3 jdk动态代理实现AOP

    CachedProviderHandler.java

    package com.java.proxy.dynamicporxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; public class CachedProviderHandler implements InvocationHandler{ private Map<String, Object> cached = new HashMap<String, Object>(); private Object target; public CachedProviderHandler(Object target) { this.target = target; } /** * invoke方法可以处理target的所有方法,这里用if判断只处理了getXXX()方法,增加了缓存功能。 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("使用动态代理了!"); Class<?>[] types = method.getParameterTypes(); if(method.getName().matches("get.+") && types.length == 1 && types[0] == String.class) { System.out.println("getXXX()方法,使用缓存"); String key = (String)args[0]; Object value = cached.get(key); if(value == null) { value = method.invoke(target, args); cached.put(key, value); } return value; } return method.invoke(target, args); } }

    工厂类ProviderFactory.java

    package com.java.proxy.dynamicporxy; import java.lang.reflect.Proxy; import com.java.proxy.FontProvider; import com.java.proxy.FontProviderFromDisk; public class ProviderFactory { public static FontProvider getFontProvider() { Class<FontProvider> targetClass = FontProvider.class; return (FontProvider)Proxy.newProxyInstance(targetClass.getClassLoader(), new Class[]{targetClass}, new CachedProviderHandler(new FontProviderFromDisk())); } }

    测试类

    package com.java.proxy.dynamicporxy; import com.java.proxy.Font; import com.java.proxy.FontProvider; public class Business { public static void main(String[] args) { FontProvider fontProvider = ProviderFactory.getFontProvider(); Font font = fontProvider.getFont("微软雅黑"); System.out.println(font); fontProvider.printName("sdfdf"); } }

    说明:jdk动态代理需要用Proxy.newProxyInstance来生成代理对象,这里有依赖被代理对象的接口,如果没有接口的就不行。

    一句话:jdk动态代理比较好用,就是获取代理对象稍显麻烦。

    1.4 cglib动态代理实现AOP

    CGLibProxy.java

    package com.java.proxy.cglib; import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class CGLibProxy implements MethodInterceptor{ @SuppressWarnings("unchecked") public <T> T getProxy(Class<T> cls) { return (T) Enhancer.create(cls, this); } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("使用cglib1"); Object result = proxy.invokeSuper(obj, args); System.out.println("使用cglib2"); return result; } }

    测试类

    package com.java.proxy.cglib; import com.java.proxy.FontProviderFromDisk; public class Business { public static void main(String[] args) { CGLibProxy cgLibProxy = new CGLibProxy(); FontProviderFromDisk proxy = cgLibProxy.getProxy(FontProviderFromDisk.class); proxy.printName("微软雅黑"); } }

    说明:cglib较前面提到的这几种实现AOP功能,是最好用的。用Enhancer.create(Class, this)获取代理对象更方便,不依赖于接口,代理功能实现起来更简单。

    2 Spring中AOP实现

    那Spring中AOP是怎么实现的呢?Spring中AOP的有两种实现方式: 1、JDK动态代理 2、Cglib动态代理

    2.1 JDK动态代理

    1.引入依赖,有spring,单元测,日志管理

    <dependencies> <!-- Spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> </dependency> <!-- 单元测试 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency> <!-- 日志 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </dependency> </dependencies>

    2.UserDao接口

    public interface UserDao { public void saveUser(); }

    3.UserDao实现类

    public class UserDaoImpl implements UserDao { @Override public void saveUser() { System.out.println("持久层:用户保存"); } }

    4.动态代理

    @Test public void test1() { final UserDao userDao = new UserDaoImpl(); // newProxyInstance的三个参数解释: // 参数1:代理类的类加载器,同目标类的类加载器 // 参数2:代理类要实现的接口列表,同目标类实现的接口列表 // 参数3:回调,是一个InvocationHandler接口的实现对象,当调用代理对象的方法时,执行的是回调中的invoke方法 //proxy为代理对象 UserDao proxy = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), new InvocationHandler() { @Override // 参数proxy:被代理的对象 // 参数method:执行的方法,代理对象执行哪个方法,method就是哪个方法 // 参数args:执行方法的参数 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("记录日志"); Object result = method.invoke(userDao, args); return result; } }); //代理对象执行方法 proxy.saveUser(); }

    5.结果 在没有修改原有类的代码的情况下,对原有类的功能进行了增强

    2.2 Cglib动态代理

    在实际开发中,可能需要对没有实现接口的类增强,用JDK动态代理的方式就没法实现。采用Cglib动态代理可以对没有实现接口的类产生代理,实际上是生成了目标类的子类来增强。  首先,需要导入Cglib所需的jar包。提示:spring已经集成了cglib,我们已经导入了spring包,所以不需要再导入其它包了。 1.创建LinkManDao类,没有实现任何接口

    public class LinkManDao { public void save(){ System.out.println("持久层:联系人保存...."); } }

    2.动态代理

    @Test public void test2() { final LinkManDao linkManDao = new LinkManDao(); // 创建cglib核心对象 Enhancer enhancer = new Enhancer(); // 设置父类 enhancer.setSuperclass(linkManDao.getClass()); // 设置回调 enhancer.setCallback(new MethodInterceptor() { /** * 当你调用目标方法时,实质上是调用该方法 * intercept四个参数: * proxy:代理对象 * method:目标方法 * args:目标方法的形参 * methodProxy:代理方法 */ @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("记录日志"); Object result = method.invoke(linkManDao, args); return result; } }); // 创建代理对象 LinkManDao proxy = (LinkManDao) enhancer.create(); proxy.save(); }

    3.结果

    3 Spring AOP实例

    3.1 基于XML配置方式

    目录结构:

    接口类 IHelloWorldService.java

    /** * 接口类 */ public interface IHelloWorldService { public void sayHello(); }

    接口实现HelloWorldService.java

    /** * 接口实现 */ public class HelloWorldService implements IHelloWorldService { @Override public void sayHello() { System.out.println("你好!Spring AOP——(即这个为主要业务)"); } }

    切面类HelloWorldAspect.java

    /** * 切面 */ public class HelloWorldAspect { /** * 前置通知 */ public void beforeAdvice(){ System.out.println("———前置通知(即先执行这里)———"); } }

    Spring AOP配置 applicationContext.xml

    <beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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="HelloWorldService" class="com.cxb.service.impl.HelloWorldService"/> <!-- 配置切面类 --> <bean id="HelloWorldAspect" class="com.cxb.aop.HelloWorldAspect"/> <!--配置AOP--> <!-- 强制使用cglib代理,如果不设置,将默认使用jdk的代理,但是jdk的代理是基于接口的 --> <aop:config proxy-target-class="true"> <!-- 定义切入点 (配置在com.cxb下所有的类在调用之前都会被拦截)--> <aop:pointcut expression="execution(* com.cxb..*.*(..))" id="HelloWorldPointcut"/> <!--切面--> <aop:aspect ref="HelloWorldAspect"> <!--配置前置通知--> <!--配置哪个切入点的哪个方法--> <aop:before pointcut-ref="HelloWorldPointcut" method="beforeAdvice"/> <!--一个切入点的引用--> </aop:aspect> </aop:config> </beans>

    3.2 基于注解方式

    使用步骤如下:

    1、引入相关jar包

    2、Spring的配置文件 applicationContext.xml 中引入context、aop对应的命名空间;配置自动扫描的包,同时使切面类中相关方法中的注解生效,需自动地为匹配到的方法所在的类生成代理对象。

    <beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" 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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"> <!-- 开启注解扫描 --> <context:component-scan base-package="com.cxb"/> <!-- 开启aop注解方式,此步骤s不能少,这样java类中的aop注解才会生效 --> <!-- 强制使用cglib代理,如果不设置,将默认使用jdk的代理,但是jdk的代理是基于接口的 --> <aop:aspectj-autoproxy proxy-target-class="true"> <aop:aspectj-autoproxy/> </beans>

    3、接口类 IHelloWorldService.java

    package com.cxb.service; /** * 接口类 * @author 蔡小波 */ public interface IHelloWorldService { public void sayHello(); }

    4.接口实现HelloWorldService.java

    package com.cxb.service.impl; import com.cxb.service.IHelloWorldService; import org.springframework.stereotype.Component; /** * 接口实现 * @author 蔡小波 */ //将实现类加入Spring的IOC容器进行管理 @Component("HelloWorldService") public class HelloWorldService implements IHelloWorldService { @Override public void sayHello() { System.out.println("你好!Spring AOP——(即这个为主要业务)"); } }

    5.切面类HelloWorldAspect.java

    要想把一个类变成切面类,需要两步, ① 在类上使用 @Component 注解 把切面类加入到IOC容器中 ② 在类上使用 @Aspect 注解 使之成为切面类

    下面直接上完整代码,用@Aspect注解方式来实现前置通知、返回通知、后置通知、异常通知、环绕通知。

    package com.cxb.aop; /** * 切面 * @author 蔡小波 */ import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; /** * 注解方式声明aop * 1.用@Aspect注解将类声明为切面(如果用@Component("")注解注释为一个bean对象(括号内容可为省略),那么就要在spring配置文件中开启注解扫描,<context:component-scan base-package="com.cxb"/> * 否则要在spring配置文件中声明一个bean对象) * 2.在切面需要实现相应方法的前面加上相应的注释,也就是通知类型。 * 3.此处有环绕通知,环绕通知方法一定要有ProceedingJoinPoint类型的参数传入,然后执行对应的proceed()方法,环绕才能实现。 */ @Component //声明这是一个组件 @Aspect ///声明这是一个切面Bean public class HelloWorldAspect { //定义切点 @Pointcut("execution(* com.cxb..*.*(..))") public void sayings(){} /** * 前置通知(注解中的sayings()方法,其实就是上面定义pointcut切点注解所修饰的方法名,那只是个代理对象,不需要写具体方法, * 相当于xml声明切面的id名,如下,相当于id="embark",用于供其他通知类型引用) * <aop:config> <aop:aspect ref="mistrel"> <!-- 定义切点 --> <aop:pointcut expression="execution(* *.saying(..))" id="embark"/> <!-- 声明前置通知 (在切点方法被执行前调用) --> <aop:before method="beforSay" pointcut-ref="embark"/> <!-- 声明后置通知 (在切点方法被执行后调用) --> <aop:after method="afterSay" pointcut-ref="embark"/> </aop:aspect> </aop:config> */ @Before("sayings()") public void sayHello(){ System.out.println("注解类型前置通知"); } //后置通知 @After("sayings()") public void sayGoodbey(){ System.out.println("注解类型后置通知"); } //环绕通知。注意要有ProceedingJoinPoint参数传入。 @Around("sayings()") public void sayAround(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("注解类型环绕通知..环绕前"); pjp.proceed();//执行方法 System.out.println("注解类型环绕通知..环绕后"); } }

    6.编写Main方法进行测试

    package com.cxb; import com.cxb.service.impl.HelloWorldService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext; /** * 测试 * 基于注解配置AOP实现 * @author 蔡小波 */ public class HelloWorldTest { public static void main(String[] args) { //这个是application容器,所以就会去所有的已经加载的xml文件里面去找,包括jar包里面的xml文件 ApplicationContext context=new FileSystemXmlApplicationContext("web/WEB-INF/applicationContext.xml"); //通过ApplicationContext.getBean(beanName)动态加载数据(类)【获取Spring容器中已初始化的bean】。 HelloWorldService helloWorld=(HelloWorldService) context.getBean("HelloWorldService"); //执行动态加载到的类的方法 helloWorld.sayHello(); } }

    运行结果:

    因为使用了注解方式,所以配置文件少了很多内容,只需要一句<context:component-scan base-package="com.cxb"/>声明要扫描的包,框架会自动扫描注释并生成bean对象。

    有个@Component("helloWorldService")这个注释,和<bean id="helloWorldService" class="com.cxb.service.impl.HelloWorldService"/>这个配置时一样的意思,

    框架会自动识别并创建名为helloWorldService的com.cxb.service.impl.HelloWorldService对象。

    所以有了注释,只需要开启注释扫描配置就好了,无需再做相同的bean配置。 

     

    Processed: 0.020, SQL: 9