1.导入aspectjweaver是spring的切入点表达式需要用的包
<dependency> <!--spring 包--> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <!--aspectjweaver是spring的切入点表达式需要用的包--> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.7</version> </dependency>2.创建业务逻辑层接口类和实现类,的增,删,改方法 3.写通知类中的具体方法 :Logger
//记录日志的工具类(也叫通知类),它里面提供了公共的代码 public class Logger { public void beforePrintlog(){ //用于打印日志:计划让其在切入点方法执行前执行,也就是前置通知 //切入点方法就是业务层的方法 System.out.println("logger类中 前置通知 方法开始记录日志了。。。"); } public void afterReturningPrintlog(){ //后置通知 System.out.println("logger类中 后置通知 方法开始记录日志了。。。"); } public void afterThrowingPrintlog(){ //异常通知 System.out.println("logger类中 异常通知 方法开始记录日志了。。。"); } public void afterPrintlog(){ //最终通知 System.out.println("logger类中 最终通知 方法开始记录日志了。。。"); } }4.配置通知,IOC,AOC,bean.xml 文件。
<?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"> <!--自己理解后的简单解释: 1.accountService 类(账户)想增强,他会的技能,并且要使用技能的时候, 都能有人监督并给出通知和建议(生成通知)。 2.就请了个可以 监督的通知类,它有通知的技能 3.通知就告诉账户,在监督账户使用技能的时候, 通知会在,账户使用具体某个技能的时候准备, 前置,后置,异常,最终 的一些相关的摄像机在进行监督。 --> <!--配置spring的IOC ,把service 对象配置进来--> <bean id = "accountService" class="wuwei.service.impl.AccountService"></bean> <!--spring中 基于xml ,AOP配置步骤 1.把通知Bean也交给spring来管理 2.使用aop:config标签表明开始AOP配置 3.使用aop:aspect标签表明配置切面 id属性:是给切面提供一个唯一标识 ref属性:是指通知类bean的id 4.在aop:aspect标签的内部使用对应的标签来配置通知的类型 我们现在示例是让pringLog方法在切入点方法执行前,所以是前置通知。 aop:before:表示配置前置通知 method属性:用于指定Logger类中哪个方法是前置通知 pointcut属性:用于指定切入点表达式,该表达式的含义是指对业务层中哪些方法增强。 切入点表达式的写法: 关键字:execution(表达式) 表达式: 访问修饰符 返回值 包名.包名..类名.方法名(参数列表) 标准示例: pointcut="execution(public void wuwei.service.impl. AccountService.saveAccount())" 全通配写法: * *..*.*(..) 切入点表达式的一般写法: 一般我们都是对业务层所有实现类的所有方法进行增强,因此切入点表达式写法通常为 <aop:pointcut expression="execution (* cn.maoritian.service.impl.*.*(..))" ></aop:pointcut> --> <!--配置Logger 通知类--> <bean id = "logger" class="wuwei.utils.Logger"></bean> <!--配置AOP--> <aop:config> <!--配置切面--> <aop:aspect id="logAdvice" ref="logger"> <!--配置前置通知的类型,并建立通知方法,和切入点方法(也就是业务逻辑层实现类中的方法)的关联--> <aop:before method="beforePrintlog" pointcut= "execution(* wuwei.service.impl.*.*(..))"></aop:before> <!--配置后置通知的类型,并建立通知方法,和切入点方法(也就是业务逻辑层实现类中的方法)的关联--> <aop:after-returning method="afterReturningPrintlog" pointcut= "execution(* wuwei.service.impl.*.*(..))"></aop:after-returning> <!--配置异常通知的类型,并建立通知方法,和切入点方法(也就是业务逻辑层实现类中的方法)的关联--> <aop:after-throwing method="afterThrowingPrintlog" pointcut= "execution(* wuwei.service.impl.*.*(..))"></aop:after-throwing> <!--配置最终通知的类型,并建立通知方法,和切入点方法(也就是业务逻辑层实现类中的方法)的关联--> <aop:after method="afterPrintlog" pointcut= "execution(* wuwei.service.impl.*.*(..))"></aop:after> </aop:aspect> </aop:config> </beans>5.测试类
public class AOPTest { public static void main(String[] args) { //1.获取容器 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //获取对象 IAccountService as = (IAccountService) ac.getBean("accountService"); //执行方法 as.saveAccount(); } }知识拓展:切入点表达式的冗长,精简
<!--配置Logger 通知类--> <bean id = "logger" class="wuwei.utils.Logger"></bean> <!--配置AOP--> <aop:config> <!--由于 pointcut属性 中的切入点表达式 过于冗长,可以提取出来, 之后再引用就行了,但配置必须在切面之前 id 是寻找的唯一标识,expression是表达式的内容 通知表达式的关键字变为pointcut-ref = "pt1"--> <aop:pointcut id="pt1" expression="execution(* wuwei.service.impl.*.*(..))"></aop:pointcut> <!--配置切面--> <aop:aspect id="logAdvice" ref="logger"> <!--配置前置通知的类型,并建立通知方法,和切入点方法(也就是业务逻辑层实现类中的方法)的关联--> <aop:before method="beforePrintlog" pointcut-ref="pt1"></aop:before> <!--配置后置通知的类型,并建立通知方法,和切入点方法(也就是业务逻辑层实现类中的方法)的关联--> <aop:after-returning method="afterReturningPrintlog" pointcut-ref="pt1"></aop:after-returning> <!--配置异常通知的类型,并建立通知方法,和切入点方法(也就是业务逻辑层实现类中的方法)的关联--> <aop:after-throwing method="afterThrowingPrintlog" pointcut-ref="pt1"></aop:after-throwing> <!--配置最终通知的类型,并建立通知方法,和切入点方法(也就是业务逻辑层实现类中的方法)的关联--> <aop:after method="afterPrintlog" pointcut-ref="pt1"></aop:after> </aop:aspect> </aop:config> </beans>四、动态代理回顾 1、jdk动态代理
a. jdk动态代理: 基于接口的动态代理 b. @Test public void testJDKProxy(){ //真实的对象 NewSale newSale = new NewSaleImpl(); //创建代理对象-- 本质上就是接口的一个实现类 //参数1: 类加载器 //参数2:类实现的接口 //参数3:真实对象的增强部分:实现了InvocationHandler接口的实现类 //匿名内部类也是该接口的实现类 NewSale sale = (NewSale) Proxy.newProxyInstance(newSale.getClass().getClassLoader(), newSale.getClass().getInterfaces(), new InvocationHandler() { /** * 增强内容 * @param proxy : 代理对象 * @param method : 代理的方法,未增强的方法 * @param args : 代理的方法的参数 * @return 代理的方法的返回值 * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //生成产品 ProductFactory productFactory = new ProductFactory(); productFactory.make(); //开始销售:通过反射执行真实对象的方法 //参数1:真实的对象 //参数2:方法的参数 method.invoke(newSale,args ); //判断是否挣钱了 //卖的价格:args[0] 假设产品的成本是 1500 if( (Float)args[0] > 1500){ //卖的价格高于成本,挣了,可卖 System.out.println("卖的价格高于成本,挣了,可卖"); }else{ //卖的价格低于成本,赔了,不可卖 System.out.println("卖的价格低于成本,赔了,不可卖"); } return null; } }); sale.sale(1000F); }2、cglib动态代理
a. cglib动态代理: 基于类的动态代理 b. 第三方jar包: cglib-2.2.2.jar c. 注意:代理的类不用final修饰 d. public void testCglibProxy(){ //真实对象 OldSale oldSale = new OldSale(); //创建cglib代理对象 //1. 创建增强类对象 Enhancer enhancer = new Enhancer(); //2. 指定代理对象的父类 enhancer.setSuperclass(oldSale.getClass()); //3. 指定增强的内容 //MethodInterceptor :接口是方法拦截器 enhancer.setCallback(new MethodInterceptor() { /*** * 增强的内容 * @param o 代理对象,增强后的对象 * @param method 代理的方法 * @param objects 代理的方法的参数 * @param methodProxy 代理方法,增强后的方法 * @return * @throws Throwable */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { //生成产品 ProductFactory factory = new ProductFactory(); factory.make(); //开始销售,执行真实对象的内容 method.invoke(oldSale, objects); //判断是否挣钱了 //卖的价格:objects[0] 假设产品的成本是 1500 if( (Float)objects[0] > 1500){ //卖的价格高于成本,挣了,可卖 System.out.println("卖的价格高于成本,挣了,可卖"); }else{ //卖的价格低于成本,赔了,不可卖 System.out.println("卖的价格低于成本,赔了,不可卖"); } return null; } }); //4. 创建代理对象: OldSale saleProxy = (OldSale)enhancer.create(); saleProxy.sale(2000f); }七、AOP的xml配置 2、配置文件
<?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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--扫描表,创建bean对象--> <context:component-scan base-package="com.itheima"></context:component-scan> <!--通知对象: 拦截到方法时,通知执行的对象--> <!-- 通知的类型 前置通知: 方法之前执行 后置通知: 方法执行完之后执行- 返回之前执行-如果有异常,则不执行 最终通知: 方法执行完后总会执行- finally 异常通知: 方法出现异常则执行 环绕通知: 前置通知+后置通知+最终通知+异常通知 --> <bean id="logger" class="com.itheima.log.Logger"></bean> <!--配置aop--> <aop:config> <!--配置切面= 切入点 + 通知 指定通知对象是谁 --> <aop:aspect ref="logger"> <!--配置切入点 id:唯一的标志 expression: 表达式 * com.itheima.service.impl.*.*(..) * com.itheima.service..*.*(..) 第一个*:代表方法任意返回值类型 第二个*: 类名任意,impl包中所有的类 第三个*: 任意方法名 (..) : 参数任意,个数任意,类型任意,顺序任意 其他的配置方式 public void com.itheima.service.impl.UserServiceImpl.findAll() void com.itheima.service.impl.UserServiceImpl.findAll() * com.itheima.service.impl.UserServiceImpl.findAll() * com.itheima.service..UserServiceImpl.findAll() : .. 代表的是包,及其子包 --> <aop:pointcut id="pointcut" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut> <!--织入: 告诉通知对象执行,具体执行哪一个方法--> <!--前置通知--> <!--<aop:before method="before" pointcut-ref="pointcut"></aop:before>--> <!--后置通知--> <!--<aop:after-returning method="afterReturning" pointcut-ref="pointcut"></aop:after-returning>--> <!--最终通知--> <!--<aop:after method="after" pointcut-ref="pointcut"></aop:after>--> <!--异常通知--> <!--<aop:after-throwing throwing="e" method="afterThrowing" pointcut-ref="pointcut"></aop:after-throwing>--> <!--环绕增强--> <aop:around method="around" pointcut-ref="pointcut"></aop:around> </aop:aspect> </aop:config> </beans>八、AOP的注解配置
package com.itheima.log; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; /** *@Component: 创建类对象 * @Aspect:配置该类为切面 * 切面是:切入点 + 通知 * * * @author 黑马程序员 * @Company http://www.ithiema.com * @Version 1.0 */ @Component @Aspect public class Logger { /** * 配置切入点 */ @Pointcut("execution(* com.itheima.service.impl.*.*(..))") public void pointcut(){}; /** * * @param joinPoint 连接点-- 拦截到的方法 */ // @Before("pointcut()") public void before(JoinPoint joinPoint){ //被代理的对象 Object target = joinPoint.getTarget(); //拦截的类的名称 String className = target.getClass().getName(); System.out.println("拦截到的类名:" +className); //获取方法对象 Signature signature = joinPoint.getSignature(); //获取方法名 String methodName = signature.getName(); System.out.println("拦截到方法名:" + methodName); System.out.println("前置通知"); } // @AfterReturning("pointcut()") public void afterReturning(){ System.out.println("后置增强"); } // @After("pointcut()") public void after(){ System.out.println("最终增强"); } // @AfterThrowing(value = "pointcut()",throwing = "e") public void afterThrowing(Exception e){ System.out.println("执行的方法的异常:"+e); System.out.println("异常通知"); } /** * ProceedingJoinPoint 可以执行拦截到方法的连接点对象 * @param joinPoint */ @Around("pointcut()") public void around(ProceedingJoinPoint joinPoint){ try { System.out.println("前置通知"); //执行原来的方法: 可以获取方法的返回值 Object result = joinPoint.proceed(); System.out.println("后置增强"); } catch (Throwable e) { System.out.println("异常通知"); // e.printStackTrace(); } finally { System.out.println("最终增强"); } } }