Spring之AOP实现讲解

目录

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配置。 

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值