概述
目录
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配置。
最后
以上就是执着裙子为你收集整理的Spring之AOP实现讲解目录1 使用AOP的4种方式2 Spring中AOP实现3 Spring AOP实例的全部内容,希望文章能够帮你解决Spring之AOP实现讲解目录1 使用AOP的4种方式2 Spring中AOP实现3 Spring AOP实例所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复