package com.dfbz.dao;import java.util.Date;/*** @author lscl* @version 1.0* @intro:*/
public class UserDao {public void save() {System.out.println("执行【save】操作 begin...,时间:【" + new Date() + "】");System.out.println("新增用户成功...");System.out.println("执行【save】操作 end...,时间:【" + new Date() + "】");}public void delete() {System.out.println("执行【delete】操作 begin...,时间:【" + new Date() + "】");System.out.println("删除用户成功...");System.out.println("执行【delete】操作 end...,时间:【" + new Date() + "】");}public void update() {System.out.println("执行【update】操作 begin...,时间:【" + new Date() + "】");System.out.println("修改用户成功...");System.out.println("执行【update】操作 end...,时间:【" + new Date() + "】");}public void query() {System.out.println("执行【query】操作 begin...,时间:【" + new Date() + "】");System.out.println("查询用户成功...");System.out.println("执行【query】操作 end...,时间:【" + new Date() + "】");}
}
package com.dfbz.test;import com.dfbz.dao.UserDao;
import org.junit.Test;/*** @author lscl* @version 1.0* @intro:*/
public class Demo01 {@Testpublic void test1() {UserDao userDao=new UserDao();userDao.save();}
}
执行效果:
需求修改如下:修改日志时间格式为yyyy-MM-dd hh:mm:ss
package com.dfbz.dao;import java.text.SimpleDateFormat;
import java.util.Date;/*** @author lscl* @version 1.0* @intro:*/
public class UserDao {private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");public void save() {System.out.println("执行【save】操作 begin...,时间:【" + sdf.format(new Date()) + "】");System.out.println("新增用户成功...");System.out.println("执行【save】操作 end...,时间:【" + sdf.format(new Date()) + "】");}public void delete() {System.out.println("执行【delete】操作 begin...,时间:【" + sdf.format(new Date()) + "】");System.out.println("删除用户成功...");System.out.println("执行【delete】操作 end...,时间:【" + sdf.format(new Date()) + "】");}public void update() {System.out.println("执行【update】操作 begin...,时间:【" + sdf.format(new Date()) + "】");System.out.println("修改用户成功...");System.out.println("执行【update】操作 end...,时间:【" + sdf.format(new Date()) + "】");}public void query() {System.out.println("执行【query】操作 begin...,时间:【" + sdf.format(new Date()) + "】");System.out.println("查询用户成功...");System.out.println("执行【query】操作 end...,时间:【" + sdf.format(new Date()) + "】");}
}
执行测试类:
需求增加:在查询操作执行之后存入缓存,增、删、改操作时清除缓存;
package com.dfbz.dao;import java.text.SimpleDateFormat;
import java.util.Date;/*** @author lscl* @version 1.0* @intro:*/
public class UserDao {private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");public void save() {System.out.println("执行【save】操作 begin...,时间:【" + sdf.format(new Date()) + "】");System.out.println("新增用户成功...");System.out.println("执行【save】操作 end...,时间:【" + sdf.format(new Date()) + "】");System.out.println("清除缓存成功...");}public void delete() {System.out.println("执行【delete】操作 begin...,时间:【" + sdf.format(new Date()) + "】");System.out.println("删除用户成功...");System.out.println("执行【delete】操作 end...,时间:【" + sdf.format(new Date()) + "】");System.out.println("清除缓存成功...");}public void update() {System.out.println("执行【update】操作 begin...,时间:【" + sdf.format(new Date()) + "】");System.out.println("修改用户成功...");System.out.println("执行【update】操作 end...,时间:【" + sdf.format(new Date()) + "】");System.out.println("清除缓存成功...");}public void query() {System.out.println("执行【query】操作 begin...,时间:【" + sdf.format(new Date()) + "】");System.out.println("查询用户成功...");System.out.println("执行【query】操作 end...,时间:【" + sdf.format(new Date()) + "】");System.out.println("存入缓存成功...");}
}
执行测试类:
针对上面的问题,我们可以使用之前学过的动态代理来解决这个问题;
package com.dfbz.dao;/*** @author lscl* @version 1.0* @intro:*/
public interface UserDaoInterface {// 新增void save();// 删除void delete();// 修改void update();// 查询void query();
}
UserDao实现UserDaoInterface接口:
package handler;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;/*** @author lscl* @version 1.0* @intro: 日志处理器,执行增删改查操作时记录日志*/
public class LoggerHandler implements InvocationHandler {private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");// 目标对象private Object target;public LoggerHandler (Object target){this.target=target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("执行【" + method.getName() + "】操作 begin...,时间:【" + sdf.format(new Date()) + "】");// 执行目标方法并获取目标方法的返回值Object returnVal = method.invoke(target, args);System.out.println("执行【" + method.getName() + "】操作 end...,时间:【" + sdf.format(new Date()) + "】");return returnVal;}
}
package handler;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;/*** @author lscl* @version 1.0* @intro: 缓存处理器: 当执行query方法时存入缓存;执行save/update/delete方法时清除缓存;*/
public class CacheHandler implements InvocationHandler {// 目标对象private Object target;public CacheHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 执行目标方法并获取目标方法的返回值Object returnVal = method.invoke(target, args);// 获取要执行的方法名称String methodName = method.getName();if ("save".equals(methodName) ||"update".equals(methodName) ||"delete".equals(methodName)) {System.out.println("清除缓存成功...");} else if ("query".equals(methodName)) {System.out.println("存入缓存成功...");}return returnVal;}
}
@Test
public void test2() {// 针对日志功能的增强(具备日志功能)UserDaoInterface userDaoLoggerProxy = (UserDaoInterface) Proxy.newProxyInstance(Demo01.class.getClassLoader(),UserDao.class.getInterfaces(),new LoggerHandler(new UserDao()));// 具备日志功能,不具备缓存功能userDaoLoggerProxy.query();System.out.println("-----------------");// 针对缓存功能的增强UserDaoInterface userDaoCacheProxy = (UserDaoInterface) Proxy.newProxyInstance(Demo01.class.getClassLoader(),UserDao.class.getInterfaces(),new CacheHandler(userDaoLoggerProxy) // 传入已经进行日志增强的代理类,继续代理加强缓存相关功能);// 具备日志和缓存功能userDaoCacheProxy.query();
}
执行效果:
AOP(Aspect Oriented Programming),即面向切面编程。通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件系统开发中的一个热点,也是Spring框架的一个重点。利用AOP可以实现业务逻辑各个部分的隔离,从而使得业务逻辑各个部分的耦合性降低,提高程序的可重用性,同时提高开发效率。
面向切面的意思简单来说就是:纵向重复,横向抽取,在我们的业务中间切一刀来填充我们的增强代码;
AOP编程操作的主要对象是切面(aspect),而切面模块化横切关注点(需要增强的方法)。
由于执行代码的时机不同,比如我们刚刚的日志增强代码在执行目标对象方法前后都需要执行,缓存代码则是在执行目标对象之后才执行。因此通知也被化分为了很多种类型:
AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,是Java社区中最完整最流行的AOP框架
在Spring2.0以上版本中,可以使用基于AspectJ注解或基于XML配置的AOP。
Tips:我们本次课程并不是使用原生的AspectJ,而是使用Spring对AspectJ的封装
切入点就是能够增强的方法的一系列集合,我们要通过表达式的方式定位一个或多个具体的连接点,这些所有的连接点被称为切入点;
execution( [权限修饰符] [返回值类型] [简单类名/全类名] [方法名] ([参数列表]) )
示例一:
execution(* com.dfbz.dao.UserDao.*(..))说明: com.dfbz.dao包下的UserDao接口中声明的所有方法// 解释:任意权限修饰符、任意方法返回值、com.dfbz.dao.UserDao接口、任意方法名、任意参数列表第一个"*"代表任意修饰符及任意返回值。
第二个"*"代表任意方法。
".."匹配任意数量、任意类型的参数。
若目标类、接口与该切面类在同一个包中可以省略包名。
示例二:
execution(public * UserDao.*(..))说明: UserDao接口的所有公有方法 // 必须public修饰、任意返回值、UserDao接口、任意方法名、任意参数列表
示例三:
execution(public String UserDao.*(..))说明: UserDao中所有返回值为String类型的共有方法方法// 必须public修饰、方法返回值必须String、UserDao接口(一级目录)、任意方法名、任意参数列表
示例四:
execution(private * UserDao.*(Integer, ..))说明: 第一个参数为Integer类型的private修饰的方法。// 必须是private修饰、任意方法返回值、UserDao接口(一级目录)、任意方法名、第一个参数必须是Integer,后面可以匹配任意参数(包含0个)
".." 匹配任意数量、任意类型的参数。
示例五:
execution(public double UserDao.*(double, double))说明: 修饰符为public,返回值为double,UserDao中的两个参数都为double的方法
// 必须是public修饰符、返回值类型是double、UserDao接口(一级目录)、任意方法名、第一个参数为double、第二个参数为double
示例六:
execution(public void *.UserDao.*(..))说明: 修饰符为public,返回值为void,任意一级包下的UserDao的任意方法
// 必须是public修饰符、返回值为void、任意一级包下的UserDao的任意方法、任意方法名、任意参数列表
注意:是任意一级包下的UserDao类,如果是多级包则不行,如:com.dfbz.dao.UserDao,必须写成public void *.*.*.UserDao.*(..)
示例七:
execution (* *.save(User,..)) || execution(* *.delete(Integer,..))说明: 任意修饰符和返回值,任意包下的任意类,第一个参数为User的save方法或者任意修饰符和返回值,任意包下的任意类,第一个参数为Integer的delete方法
Tips:当权限修饰符设置为
*
时,方法的返回值也必须为*
,而且不必使用*
显示的写出例如这样就是一种错误的写法:
execution(* String UserDao.*(..))
、execution(* * UserDao.*(..))
可以这样:
execution(* UserDao.*(..))
4.0.0 com.dfbz 01_Spring 1.0-SNAPSHOT org.springframework spring-context 5.2.9.RELEASE org.springframework spring-aspects 5.2.9.RELEASE org.springframework spring-test 5.2.9.RELEASE junit junit 4.12 test
package com.dfbz.dao;/*** @author lscl* @version 1.0* @intro:*/
public class UserDao {public void save() {System.out.println("新增用户成功...");}public void delete(Integer id) {System.out.println("删除用户【" + id + "】成功...");}public void update() {System.out.println("修改用户成功...");}public String query() {System.out.println("查询用户成功...");return "success";}
}
package com.dfbz.aspect;import org.aspectj.lang.ProceedingJoinPoint;/*** @author lscl* @version 1.0* @intro: 针对UserDao进行增强的切面类*/
public class MyAspect {public void before(){System.out.println("前置通知...");}public Object around(ProceedingJoinPoint around) throws Throwable { //环绕通知System.out.println("环绕通知之前...");// 执行下一个切面的通知,如果没有下一个切面那么执行目标方法Object proceed = around.proceed(); System.out.println("环绕通知之后...");return proceed; //将目标方法的返回值返回}public void after(){System.out.println("后置通知...");}public void afterReturning(){System.out.println("返回通知...");}public void afterThrowing(){System.out.println("异常通知...");}
}
切面相关配置:
开启AOP代理,当Spring IOC容器侦测到bean配置文件中的
元素时,会自动为与AspectJ切面匹配的bean创建代理
:aop核心配置,在此标签内配置切点、切面等
:配置切入点
:配置切面,并指定切面
:前置通知,指定前置通知方法和采用哪个切点
:环绕通知
:后置通知
:后置返回通知
:异常通知spring.xml:
package com.dfbz.demo;import com.dfbz.dao.UserDao;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;/*** @author lscl* @version 1.0* @intro:*/
@RunWith(SpringRunner.class)
@ContextConfiguration("classpath:spring.xml")
public class Demo01 {@Autowiredprivate UserDao userDao;@Testpublic void test1()throws Exception{userDao.save();}
}
执行效果:
执行目标方法出现异常的执行情况:
关于通知的执行顺序,每个版本都有很大的改动,并且配置顺序不一样也会导致通知执行顺序不一致,再者注解方式和配置文件方式的执行运行也会有些不同,因此我们不需要死记各个通知的执行顺序;通常情况下,通知执行的顺序是:
正常情况:前置通知—>环绕通知—>目标方法—>环绕通知—>返回通知—>后置通知
出现异常情况:前置通知—>环绕通知—>目标方法—>环绕通知—>后置通知—>异常通知
但是如果更改了切面中配置通知的顺序,则执行的顺序会有所改变,并且没有什么规律;我们一般不用太在意通知的执行顺序,因为前置通知和后置通知一定在目标方法执行前后;
Tips:当执行目标方法出现异常时,不会有目标对象不会有返回值,因此返回通知不会被执行;
ProceedingJoinPoint类是AOP编程中用于获取目标对象/方法信息的一个类;提供的方法如下:
getStaticPart()
:获取切入点表达式getTarget()
:获取目标对象getThis()
:获取当前对象(代理对象)getArgs()
:获取代理对象在调用方法时传递的参数getSignature()
:获取签名proceed()
:执行目标方法Signature主要是描述目标对象的类;其提供的常用方法如下:
getDeclaringType()
:获取目标对象getDeclaringTypeName()
:获取目标对象全类名getName()
:获取目标方法名称测试代码:
public Object around(ProceedingJoinPoint joinPoint) throws Throwable { // 需要执行目标方法// 切入点表达式(execution(void com.dfbz.dao.UserDao.query()))System.out.println(joinPoint.getStaticPart());// 目标对象(class com.dfbz.dao.UserDao)System.out.println(joinPoint.getTarget().getClass());// 获取到代理对象(class com.dfbz.dao.UserDao$$EnhancerBySpringCGLIB$$4354d856)System.out.println(joinPoint.getThis().getClass());// 代理对象在调用方法时传递的参数System.out.println(Arrays.toString(joinPoint.getArgs()));System.out.println("----------------------");// 获取方法签名Signature signature = joinPoint.getSignature();// 获取目标对象(class com.dfbz.dao.UserDao)System.out.println(signature.getDeclaringType());// 获取目标对象全类名(com.dfbz.dao.UserDao)System.out.println(signature.getDeclaringTypeName());// 获取执行目标方法的名称(query)System.out.println(signature.getName()); // 执行下一个切面的通知,如果没有下一个切面那么执行目标方法,并获取目标方法的返回值Object result = joinPoint.proceed();// 将目标方法的返回值原封不动的返回了给了方法的调用者return result;
}
执行结果如下:
package com.dfbz.aspect;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;/*** @author lscl* @version 1.0* @intro: 针对UserDao进行增强的切面类*/
@Aspect // 标注这是一个配置类
@Component // 交给IOC管理
public class MyAspect {// 定义切点@Pointcut("execution(* com.dfbz.dao.UserDao.save())")public void pt(){}// @Before("execution(* com.dfbz.dao.UserDao.*())") // 单独定义切点@Before("pt()") // 引用定义的切点public void before(){System.out.println("前置通知...");}@Around("pt()")public Object around(ProceedingJoinPoint around) throws Throwable { //环绕通知System.out.println("环绕通知之前...");Object proceed = around.proceed(); //执行目标方法System.out.println("环绕通知之后...");return proceed; //将目标方法的返回值返回}@After("pt()")public void after(){System.out.println("后置通知...");}@AfterReturning("pt()")public void afterReturning(){System.out.println("返回通知...");}@AfterThrowing("pt()")public void afterThrowing(){System.out.println("异常通知...");}
}
在注解情况下,通知的顺序如下:
Tips:在注解配置情况下,执行完后置通知又回到环绕通知了;所以环绕通知中我们一般不执行代码,只执行目标对象的方法;
分析:主要就是
改为注解配置
配置类:
/*** @author lscl* @version 1.0* @intro:*/
@ContextConfiguration
@ComponentScan("com.dfbz")
@EnableAspectJAutoProxy // 开启AOP自动代理(是否启用CGLIB代理)
public class SpringConfig {}
多个切面执行顺序可以由@Order()
注解来配置,数字越小,越先执行,多个切面执行顺序如下:
package com.dfbz.aspect;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;/*** @author lscl* @version 1.0* @intro:*/
@Aspect
@Component
@Order(1) // 切面执行顺序,数字越小优先级越高
public class MyAspect_01 {// 定义切点@Pointcut("execution(* com.dfbz.dao.UserDao.save())")public void pt() {}@Before("pt()")public void before() {System.out.println("【MyAspect_01】前置通知...");}@Around("pt()")public Object around(ProceedingJoinPoint around) throws Throwable {Object proceed = around.proceed();return proceed;}@After("pt()")public void after() {System.out.println("【MyAspect_01】后置通知...");}@AfterReturning("pt()")public void afterReturning() {System.out.println("【MyAspect_01】返回通知...");}@AfterThrowing("pt()")public void afterThrowing() {System.out.println("【MyAspect_01】异常通知...");}
}
package com.dfbz.aspect;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;/*** @author lscl* @version 1.0* @intro:*/
@Aspect
@Component
@Order(10) // 切面执行顺序,数字越小优先级越高
public class MyAspect_02 {// 定义切点@Pointcut("execution(* com.dfbz.dao.UserDao.save())")public void pt() {}@Before("pt()")public void before() {System.out.println("【MyAspect_02】前置通知...");}@Around("pt()")public Object around(ProceedingJoinPoint around) throws Throwable {Object proceed = around.proceed();return proceed;}@After("pt()")public void after() {System.out.println("【MyAspect_02】后置通知...");}@AfterReturning("pt()")public void afterReturning() {System.out.println("【MyAspect_02】返回通知...");}@AfterThrowing("pt()")public void afterThrowing() {System.out.println("【MyAspect_02】异常通知...");}
}
package com.dfbz.dao;import org.springframework.stereotype.Repository;/*** @author lscl* @version 1.0* @intro:*/
@Repository
public class UserDao {public void save() {System.out.println("新增用户成功...");}public void delete(Integer id) {System.out.println("删除用户【" + id + "】成功...");}public void update() {System.out.println("修改用户成功...");}public String query() {System.out.println("查询用户成功...");return "success";}
}
package com.dfbz;import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.test.context.ContextConfiguration;/*** @author lscl* @version 1.0* @intro:*/
@ContextConfiguration
@ComponentScan("com.dfbz") // 扫包
@EnableAspectJAutoProxy // 开启Aop自动代理
public class SpringConfig {
}
package com.dfbz.demo;import com.dfbz.SpringConfig;
import com.dfbz.dao.UserDao;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;/*** @author lscl* @version 1.0* @intro:*/
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class Demo01 {@Autowiredprivate UserDao userDao;@Testpublic void test1()throws Exception{userDao.save();}
}
【案例】我们使用我们学习的AOP完成前面的案例需求;
我们需要定义两个切面类:分别是日志处理切面类,缓存处理切面类;
根据多个切面的执行顺序,我们选择缓存切面类执行顺序为1,日志切面类执行顺序为2(后执行);
package com.dfbz.aspect;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;/*** @author lscl* @version 1.0* @intro: 缓存功能切面类*/
@Aspect
@Component
@Order(1) // 多个切面执行的顺序,数字越小越先执行
public class CacheAspect {@Pointcut("execution(* com.dfbz.dao.UserDao.*(..))")public void pt() {}private ProceedingJoinPoint pjp;@Around("pt()")public Object around(ProceedingJoinPoint pjp) throws Throwable{this.pjp=pjp;return pjp.proceed(); // 执行目标方法}@AfterReturning("pt()") // 返回通知(环绕通知执行完毕之后执行)public void afterReturning() {String methodName = pjp.getSignature().getName();if ("save".equals(methodName) ||"update".equals(methodName) ||"delete".equals(methodName)) {System.out.println("清除缓存成功...");} else if ("query".equals(methodName)) {System.out.println("存入缓存成功...");}}}
package com.dfbz.aspect;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;import java.text.SimpleDateFormat;
import java.util.Date;/*** @author lscl* @version 1.0* @intro:*/
@Aspect
@Component
@Order(2)
public class LoggerAspect {private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");@Pointcut("execution(* com.dfbz.dao.UserDao.*(..))")public void pt(){};private ProceedingJoinPoint pjp;@Before("pt()")public void before(){// 目标方法的名称String methodName = pjp.getSignature().getName();System.out.println("执行【" +methodName+ "】操作 begin...,时间:【" + sdf.format(new Date()) + "】");}@Around("pt()")public Object around(ProceedingJoinPoint pjp) throws Throwable{this.pjp=pjp;// 执行目标方法Object returnVal = pjp.proceed();return returnVal;}@AfterReturning("pt()")public void afterReturning(){ // 返回通知// 目标方法的名称String methodName = pjp.getSignature().getName();System.out.println("执行【" +methodName+ "】操作 begin...,时间:【" + sdf.format(new Date()) + "】");}
}
执行测试类:
我们之前学习过JDK动态代理,要求目标对象一定要实现接口,否则不能用动态代理。如果目标对象就是没有实现接口,还想对目标对象某个方法进行扩展,那么JDK的动态代理就不行了;
Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展;
Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口。它广泛的被许多AOP的框架使用,为他们提供方法的interception(拦截),Spring-Core核心已经包含CGLIB功能。
package com.dfbz.dao;import org.springframework.stereotype.Repository;/*** @author lscl* @version 1.0* @intro: 目标对象*/
@Repository
public class UserDao implements UserDaoInterface {public void save(){System.out.println("save..");}public void delete(){System.out.println("delete..");}public void update(){System.out.println("update..");}public void query(){System.out.println("query..");}
}
package com.dfbz.test;import com.dfbz.dao.UserDao;
import org.junit.Test;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;import java.lang.reflect.Method;/*** @author lscl* @version 1.0* @intro:*/
public class Demo04 {@Testpublic void test1() {//返回一个service的子类UserDao proxy = (UserDao) Enhancer.create(UserDao.class, new MethodInterceptor() {/*** proxy:代理对象* method:要执行的方法对象* args:要执行的方法的参数* methodProxy:代理对象的方法对象* */@Overridepublic Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("增强的代码");Object invoke = method.invoke(new UserDao());/*methodProxy:代理对象的方法类,因为cglib代理对目标对象进行继承派生出子类,因此代理对象是目标对象的子类此代理对象方法类也可以执行本代理对象方法*/
// Object invoke = methodProxy.invoke(new UserDao(), args);System.out.println("增强的代码");return invoke;}});proxy.save();}}
Tips:cgblib代理类时,这个类不能被final修饰;
刚刚我们学的AOP默认采用的是JDK动态代理,也就是说Spring会帮我们使用JDK动态代理出来一个UserDaoInterface的子类注入到UserDaoInterface中,如果我们改成了UserDao来接收,那么则会报错;因为JDK的动态代理是不能针对类进行代理的;
但需要注意的是:如果目标类没有实现任何接口的话,即使设置为false,还是会采用GBLIB代理
修改代理方式:
编写一个切面:
package com.dfbz.aspect;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;/*** @author lscl* @version 1.0* @intro: 编写切面(切面 = 切入点 + 通知)*/
@Component
@Aspect
public class MyAspect {// 配置切入点@Pointcut("execution(* com.dfbz.dao.UserDao.*(..))")public void pt(){}@Around("pt()")public Object around(ProceedingJoinPoint pjp) throws Throwable { // 需要执行目标方法// 执行目标方法Object result = pjp.proceed();return result;}
}
编写接口:
package com.dfbz.dao;/*** @author lscl* @version 1.0* @intro:*/
public interface UserDaoInterface {public void save();public void delete();public void update();public void query();
}
让UserDao实现该接口;
修改为false(采用JDK代理):
修改为true(采用GBLIB代理):