AOP编程引入-CGLIB动态代理
cglib动态代理
在目标对象有接口时可以使用jdk动态代理,
当目标对象没有接口时就不能使用jdk动态代理,而是要使用cglib动态代理
cglib动态代理
含义:就是在运行时期动态生成一个类的子类的方式实现对目标对象的扩展
代码演示
package com.lq.proxy;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class ProxyFactoryByCglib {
//使用cglib对目标增强
public static Object getProxy(Object target) {
//cglib代理
// target.getClass():需要增强的目标字节码对象
// new MethodInterceptor(),代理对象方法拦截器,与jdk动态代理中事件处理程序功能一样
return Enhancer.create(
target.getClass(),
new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//o, 代理对象
//method,目标对象的反射方法对象
//objects,目标对象方法的参数数组
//methodProxy,代理对象的方法,没有任何用处
//增强1:输出日志之前
System.out.println("日志之前");
//执行目标的方法
Object result = method.invoke(target, objects);
//增强2:输出日志之后
System.out.println("日志之后");
return result;
}
}
);
}
}
测试代码
//cglib动态代理测试
@Test
public void test2(){
//创建目标对象
UserServiceImpl2 userService = new UserServiceImpl2();
//对目标对象增强返回代理对象
UserServiceImpl2 proxy = (UserServiceImpl2) ProxyFactoryByCglib.getProxy(userService);
System.out.println(proxy.getClass());
//调用代理对象的方法
proxy.save();
}
01、Aop编程(一)基本概念
目标
-
什么是切面?
-
什么是面向切面编程?
-
面向切面编程好处?
概念
-
AOP (Aspect Oriented Programming) 即面向切面编程
作用: 可以在不修改原有代码的基础上,对目标方法进行扩展增强。
-
通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术
原理:动态代理
特点
- 非侵入式编程: 在不修改源码的情况下对已有方法进行增强
- 提高代码复用: 增强的内容抽象成方法或者对象可重复使用
- 统一管理维护: 抽象成独立的方法或对象方便后期维护管理
原理
- Spring AOP 实现的原理是动态代理技术
- 底层支持两种动态代理
- 当目标实现接口时采用JDK动态代理
- 当目标没有实现接口采用Cglib动态代理(可配置统一使用Cglib)
术语
-
Joinpoint(连接点):
在spring中,连接点指的都是方法(指的是那些要被增强功能的候选方法),spring只支持方法类型的连接点。
-
Pointcut(切入点)
所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。
-
Advice(通知/增强)
所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。 通知的类型: 前置通知,后置通知,异常通知,最终通知,环绕通知。
-
Target(目标对象)
被代理的对象. 比如:对业务类增强,目标对象就是业务类
-
Weaving(织入):
织入指的是把增强用于目标对象,创建代理对象的过程。spring采用动态代理织入,AspectJ采用编译期织入和类装载期织入。
-
Proxy(代理):
一个类被AOP织入增强后,即产生一个结果代理类。比如动态代理案例中的经纪人。
-
Aspect(切面)
切面指的是切入点和通知的结合。
回答什么是切面编程
aop就是切面编程,面向重复的代码编程,重复的代码只需要写一次,
切面编程有:
提高复用
降低代码的冗余
解耦
不会进行侵入式编程,不会改变原来的代码
切入点表达式
是用来生成代理对象
语法是 " ?零或1 "
execution(
modifiers-pattern? 方法的访问修饰符(可选)
ret-type-pattern 方法返回值类型(必填)
declaring-type-pattern?name-pattern(param-pattern) 包名类名(可选)方法名称(参数)
throws-pattern? 方法的异常(可选)
)
应用
expression="execution(* save(..))"/>
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" 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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--1. 创建目标对象-->
<bean id="userService" class="com.itheima.servcie.impl.UserServiceImpl"></bean>
<!--2. 创建切面类对象-->
<bean id="logAspect" class="com.itheima.aop.LogAspect"></bean>
<!--3.AOP配置: 目标将通知织入到切入点上-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pt" expression="execution(* com.itheima.servcie.impl.UserServiceImpl.save(..))"/>
<!--配置切面:将通知织入到切入点上
<aop:aspect ref="logAspect"> 配置引用上面的切面类对象
-->
<aop:aspect ref="logAspect">
<!--<aop:before> 配置前置通知,用于在目标方法前面增强-->
<aop:before method="insertLog" pointcut-ref="pt"></aop:before>
</aop:aspect>
</aop:config>
</beans>
图片
切入点表达式
<!--切入点表达式
目标方法
public void save() {
System.out.println("保存用户成功!");
}
作用:查找最终要增强的方法
格式:execution( 方法修饰符 方法返回值 包名.类名.方法名(参数列表))
1) 方法修饰符
格式1:public 精确匹配
格式2:不写,代表匹配所有方法修饰符
2) 方法返回值
格式1:void 精确匹配
格式2:* 代表匹配任何类型的返回值
3) 包名
格式1:com.itheima.servcie.impl 精确匹配
格式2:com.itheima.*.* 包里面的每个*代表包的一个层级名任意,不代表所有包名任意
4) 类名
格式1: UserServiceImpl 精确匹配
格式2: *ServiceImpl 匹配以什么什么结尾的类名
格式3: * 匹配任意类名
5) 方法名
格式1: save 精确匹配
格式2: save* 匹配以什么什么开头的方法名
格式3: * 匹配任意方法名
注意:每一层都可以含有*,但是一个*不代表所有包名
6) 参数列表
目标方法
public void save(String name,Integer age) {
System.out.println("保存用户成功!");
}
格式1(有参数): String,Integer 精确匹配
格式2: .. 匹配任意个数的参数
推荐使用的切入点表达式:
第一个:* com.itheima.servcie.impl.*.*(..)) 对所有业务类里面所有方法进行增强
第二个:* com.itheima.servcie.impl.*ServiceImpl.*(..)) 对所有业务类里面所有方法进行增强
-->
AOP常用标签的说明
Aop编程(四)常用标签说明
`<aop:config>`
作用:声明aop配置。
`<aop:aspect>`
作用:配置切面。
属性:
id:唯一标识切面的名称
ref:引用通知类bean的id
`<aop:pointcut>`
作用:配置切入点表达式。
属性:
id:唯一标识切入点表达式名称
expression:定义切入点表达式
`<aop:before>`
作用:配置前置通知
属性:
method:指定通知方法名称
pointcut:定义切入点表达式
pointcut-ref:引用切入点表达式的id
`<aop:after-returning>`
作用:配置后置通知
属性:
method:指定通知方法名称
pointcut:定义切入点表达式
pointcut-ref:引用切入点表达式的id
`<aop:after-throwing>`
作用:配置异常通知
属性:
method:指定通知方法名称
pointcut:定义切入点表达式
pointcut-ref:引用切入点表达式的id
`<aop:after>`
作用:配置最终通知
属性:
method:指定通知方法名称
pointcut:定义切入点表达式
pointcut-ref:引用切入点表达式的id
`<aop:around>`
作用:配置环绕通知
属性:
method:指定通知方法名称
pointcut:定义切入点表达式
pointcut-ref:引用切入点表达式的id
通知类型
<aop:before> 配置前置通知
<aop:after-returning> 后置通知
<aop:after-throwing> 异常通知
<aop:after> 最终通知
前置:在目标方法执行之前。<aop:before/>
后置:在目标方法成功执行之后。<aop:after-retuning/>
异常:在目标方法执行出错之后。<aop:after-throwing/>
最终:无论目标方法执行成功与否。<aop:after/>
环绕的通知
优势:可以获得目标对象方法信息(方法名,参数.....)
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--1.创建目标对象UserServiceImpl-->
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl"></bean>
<!--2.创建切面类LogAspect对象-->
<bean id="logAspect" class="com.itheima.aop.LogAspect"></bean>
<!--3.aop配置:进行增强-->
<aop:config>
<aop:pointcut id="pt" expression="execution(* com.itheima.service.impl.*.*(..))"/>
<!--3.2 配置切面,将通知织入到切入点上-->
<aop:aspect ref="logAspect">
<!--
try{
【前置通知】
执行目标对象方法
【后置通知】
}catch(){
【异常通知】
}finally{
【最终通知】
}
-->
<!-- <!–前置通知–>
<aop:before method="before" pointcut-ref="pt"></aop:before>
<!–后置通知–>
<aop:after-returning method="afterReturning" pointcut-ref="pt"></aop:after-returning>
<!–异常通知–>
<aop:after-throwing method="afterThrowing" pointcut-ref="pt"></aop:after-throwing>
<!–最终通知–>
<aop:after method="after" pointcut-ref="pt"></aop:after>-->
<!--环绕通知【推荐】-->
<aop:around method="around" pointcut-ref="pt"></aop:around>
</aop:aspect>
</aop:config>
</beans>
环绕通知,优点灵活好用,开发中使用环绕通知
Aop 编程注解方式实现【应用】
1、创建项目 spring03_04_aop_anno
2、service类使用注解创建对象
3、修改切面类
4、bean.xml修改配置文件
实现
1、创建项目 spring03_04_aop_anno
2、service类使用注解创建对象
package com.itheima.servcie.impl;
import com.itheima.servcie.UserService;
import org.springframework.stereotype.Service;
/**
* @author 黑马程序员
*/
@Service
public class UserServiceImpl implements UserService {
@Override
public void save(String name,Integer age) {
System.out.println("保存用户成功!");
//int a = 1/0;
}
}
3、修改切面类
package com.itheima.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* @author 黑马程序员
*/
@Component
@Aspect //切面类
public class LogAspect {
//切入点表达式注解
@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
public void pointcut(){}
//前置通知方法
//@Before("pointcut()")
public void before(){
System.out.println("前置通知...写入日志");
}
//后置通知方法
//@AfterReturning("pointcut()")
public void afterReturning(){
System.out.println("后置通知...写入日志");
}
//异常通知方法
//@AfterThrowing("pointcut()")
public void afterThrowing(){
System.out.println("异常通知...写入日志");
}
//最终通知方法
//@After("pointcut()")
public void after(){
System.out.println("最终通知...写入日志");
}
//环绕通知
//返回值:Object,设置目标方法的返回类型,可以接收一切返回类型
//参数:ProceedingJoinPoint 目标方法描述对象,可以获取目标方法名字,参数列表,返回值
@Around("pointcut()")
public Object around(ProceedingJoinPoint pjp){
Object result = null;
try {
//1.前置通知
System.out.println("环绕前置通知...写入日志");
//执行目标方法
result = pjp.proceed();
//环绕通知优势:可以获取目标方法信息
//目标方法名字
String methodName = pjp.getSignature().getName();
System.out.println("方法名字:"+methodName);
//参数列表
Object[] args = pjp.getArgs();
System.out.println("方法参数:"+Arrays.toString(args));
//2.后置通知
System.out.println("环绕后置通知...写入日志");
} catch (Throwable e) {
e.printStackTrace();
//3.异常通知
System.out.println("环绕异常通知...写入日志");
} finally {
//4.最终通知
System.out.println("环绕最终通知...写入日志");
}
return result;
}
}
4、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"
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 https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--开启IOC注解扫描-->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!--开启aop注解扫描-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
测试:
package com.itheima.test;
import com.itheima.service.UserService;
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.SpringJUnit4ClassRunner;
/**
* @author 黑马程序员
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:bean.xml")
public class AppTest {
@Autowired
private UserService userService;
@Test
public void saveTest(){
//业务方法调用
userService.save("播仔",15);
System.out.println(userService.getClass());
}
}
Aop零配置实现
就是创建一个配置类
先用@Configuration声明代替bean.xml文件
@ConponentScan来代替基 包扫描
@EnableAspectJAutoProxy代替AOP注解扫描
总结(面试)
1)AOP概念
AOP(Aspect Oriented Programiming )面向切面编程
2)AOP作用
在不修改源码的情况下,可以对目标对象的方法进行增强
3)AOP应用场景
日志记录
事务控制
性能监控
权限控制
4)Spring AOP底层原理
动态代理
JDK动态代理: 在目标对象实现了接口的情况下(class com.sun.proxy.$Proxy7)
Cglib动态代理:在目录对象没有实现接口的情况下 (class cn.itcast.service.impl.UserServiceImpl$$EnhancerBySpringCGLIB$$2d984d03)
5)SpringAOP用法(上午重点掌握)
1)XML【推荐】
bean.xml
<aop:config>
<aop:pointcut id="" expression="xx"/>
<aop:aspect ref="logAspect">
<aop:before method="xx" pointcut-ref=""/>
</aop:aspect>
</aop:config>
2)混用使用(XML+注解)【推荐,推荐使用环绕通知】
LogAspect 切面类
@Aspect @Pointcut @Before/@After…
bean.xml:
aop:aspectj-autoproxy
3)纯注解(SpringBoot开发推荐)
LogAspect 切面类
@Aspect @Pointcut @Before/@After....
SpringConfiguration 配置类:
@Configuration @ComponentScan @EnableAspectJAutoProxy