文章目录
前言
spring默认支持方法级别的连接点
一、AOP的作用:
进行权限校验、日志、和事务处理、性能监控、方法增强
二、为什么出现AOP:
1、aop面向切面编程,降低程序的耦合性,提高程序的复用性和开发效率。
2、通俗易懂的话就是不修改源代码,增强功能,可以理解为对相对应方法进行修饰。
三、AOP 的开发中的相关术语:
Aspect(切面): 将横切关注点逻辑进行模块封装的实体对象,封装了切点和通知代码的类。
Advice(通知/增强):切面的工作,给被增强的区域增强的逻辑称为通知。(前置通知、后置通知、环绕通知、返回通知、异常通知)
Joinpoint(连接点): 允许使用通知的地方
Pointcut(切入点):定义一系列规则对joinPoint进行筛选,实际使用通知的地方。
总结关系:apsect是装满导弹的巡洋舰,advice是要发射的导弹,joinPoint是可能打击的地点,
PointCut是实际打击的地方。
Target(目标对象):符合pointcut条件,要被横切逻辑织入的对象,代理的目标对象
Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程.
织入器:完成织入过程的执行这,例如ajc
Introduction :引入型advice
1、为目标类引入新方法或属性,不用目标类做任何实现和继承。
2、使得目标类在使用过程中转型成新接口对象,调用新接口方法。
引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类
动态地添加一些方法或 Field.
2、织入的时期
- 编译期:切面在目标类编译时期织入,需要特殊的编译期,AspectJ的织入编译器就是这种方式。
- 类加载器:切面在目标类加载到JVM时织入,需要特殊的类加载器,可以将目标类的字节码增强。Aspect5的加载时织入。
- 运行期:一般情况下,织入切面时,aop容器会为目标类动态创建一个代理对象,spring aop就是这种方式。
四、切面类:
切入点表达式
(1)切入点表达式作用:知道对哪个类里面的哪个方法进行增强
(2)语法结构: execution([权限修饰符] [返回类型] [类全路径] [方法名称]([参数列表]) )
举例 1:对 com.zixue.Test.Logo 类里面的 add 进行增强
execution(* com.zixue.Test.Logo.test(..))
举例 2:对 com.zixue.Test.Logo 类里面的所有的方法进行增强
execution(* com.zixue.Test.Logo.* (..))
举例 3:对 com.zixue.dao 包及子包所有类,类里面所有方法进行增强
execution(* com.zixue.dao..*.* (..))
1、注解实现增强:
①被增强的类
@Component
public class Logo {
public void test(){
System.out.println("测试。。。test");
}
}
②切面类:
@Order(1)//在增强类上面添加注解 @Order(数字类型值),数字类型值越小优先级越高
@Aspect //切面类,生成代理对象
@Component
public class MyAspect {
@Pointcut("execution(* com.zixue.Test.Logo.test(..))")//定义切入点的注解
private void pointcut() {
}
@Before(value = "MyAspect.pointcut()")//前置通知
public void before() {
System.out.println("before===========");
}
//后置通知,出现异常则不执行如下代码
@AfterReturning(value = "MyAspect.pointcut()",returning = "result")
public void afterReturning(Object result) {
System.out.println("afterReturning==========="+result);
}
@Around("MyAspect.pointcut()")//环绕通知
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("around==========");
Object obj = joinPoint.proceed();
System.out.println("around==========");
return obj;
}
@AfterThrowing(value = "MyAspect.pointcut()",throwing = "exception")//异常抛出通知.
public void afterThrowing(Exception exception) {
System.out.println("afterThrowing========"+exception);
}
@After(value = "MyAspect.pointcut()") //最终通知,无论异常与否,都将运行
public void after(JoinPoint joinPoint) {
System.out.println("after=========="+joinPoint.getSignature().getName());
}
}
③配置类:
//可以替代application.xml中<aop:aspectj-autoproxy/>配置aop注解
@EnableAspectJAutoProxy //<!-- 开启 Aspect 生成代理对象-->
@Configuration //配置类 = 配置文件
public class MainConfig {
@Bean("persion") //将bean注入ioc容器,类型为返回值类型,id默认为方法名
public Person person(){
return new Person("li",20);
}
④测试:
public class AopDemo {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
Logo log = applicationContext.getBean(Logo.class);
log.test();
}
}
结果:
around==========
before===========
测试。。。test
around==========
after==========test
afterReturning===========2
2、AOP 的xml配置:
<!-- 配置切面类 -->
<bean id="myAspect" class="cn.zixue.MyAspect"></bean>
<!-- 进行 aop 的配置 -->
<aop:config>
<!-- 配置切入点表达式:哪些类的哪些方法需要进行增强 -->
<aop:pointcut expression="execution(*cn.zixue.*Dao.save(..))" id="pointcut1"/>
<aop:pointcut expression="execution(*cn.zixue.*Dao.delete(..))" id="pointcut2"/>
<aop:pointcut expression="execution(*cn.zixue.*Dao.update(..))" id="pointcut3"/>
<aop:pointcut expression="execution(*cn.zixue.*Dao.find(..))" id="pointcut4"/>
<!-- 配置切面 -->
<aop:aspect ref="myAspect">
<aop:before method="before" pointcut-ref="pointcut1"/>
<aop:after-returning method="afterReturing" pointcut-ref="pointcut2"/>
<aop:around method="around" pointcut-ref="pointcut3"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut4"/>
<aop:after method="after" pointcut-ref="pointcut4"/>
</aop:aspect>
</aop:config>
总结:
1)、将业务逻辑组件和切面类都加入到容器中;告诉Spring哪个是切面类(@Aspect)
2)、在切面类上的每一个通知方法上标注通知注解,告诉Spring何时何地运行(切入点表达式)
3)、开启基于注解的aop模式;@EnableAspectJAutoProxy
五、aop的原理:动态代理
动态代理机制:
1、通过包名的类获对应class二进制字节流
2、根据读取的字节流,将代表的静态存储结构转换成运行时数据结构
3、生成该类的class对象,作为该类在方法区的数据访问入口
4、根据一定规则去改动或生成新的字节流,将切面逻辑织入其中
5、根据目标类或者接口,计算出代理类的字节码并加载到jvm中
spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装在期织入
Proxy(代理) :一个类被 AOP 织入增强后,就产生一个结果代理类
Spring 的 AOP 的底层用到两种代理机制:(运行时动态生成字节码,并加载到JVM,没有实际class文件)
* JDK 的动态代理 :针对实现了接口的类产生代理,最终生成的代理类和目标类实现相同的接口
在com.sun.proxy包下,类名为$proxy2。
* Cglib 的动态代理 :针对没有实现接口的类产生代理. 应用的是底层的字节码增强的技术 生成当前类
的子类对象,它及支持接口也支持没有接口的类,最终生成的代理类会继承目标类,并且和目标类在相同的包下。
1、jdk接口代理
//和aspect一样,包装横切逻辑
public class MyJDKProxy implements InvocationHandler {
//目标类对象
private UserDao userDao;
public MyJDKProxy(UserDao userDao) {
this.userDao = userDao;
}
@Override//proxy方便获取到代理类对象
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("add".equals(method.getName())){
System.out.println("加方法增强============proxy:"+ proxy + " userDao: " +userDao);
}//调用目标类对象的方法
return method.invoke(userDao,args);
}
}
public static void main(String[] args) {
// 编写工具方法:生成代理:
UserDao userDao = new UserDaoImpl();
UserDao userDaoProxy = (UserDao)Proxy.newProxyInstance(userDao.getClass().getClassLoader(),
userDao.getClass().getInterfaces(), new MyJDKProxy(userDao));
userDaoProxy.add();
}
//控制台输出:====================================================================
加方法增强============proxy:com.atguigu.spring5.dao.UserDaoImpl@ea30797
userDao: com.atguigu.spring5.dao.UserDaoImpl@ea30797
dao add.....
实现了InvocationHandler接口的代理类,调用方法是会调用invoke方法从而实现增强。
2、cglib代理:子类增强
cglib是代码生成库:code generation library
1、目标类可是实现接口,或者没有实现接口的类
2、内部主要封装了ASM java字节码操控框架
3、通过生成目标类(非static、final修饰的类)的子类,覆盖非final的方法,绑定勾子回调自定义拦截器
4、Enhancer创建一个被代理对象的子类并且拦截所有的方法调用(包括从Object中继承的toString和
hashCode方法)。Enhancer不能够拦截final方法,例如Object.getClass()方法,这是由于Java final
方法语义决定的。
1.2.5.2 Cglib 动态代理增强一个类中的方法:
public class MyCglibProxy implements MethodInterceptor{
private CustomerDao customerDao;
public MyCglibProxy(CustomerDao customerDao){
this.customerDao = customerDao;
}
// 生成代理的方法:
public CustomerDao createProxy(){
// 创建 Cglib 的核心类:
Enhancer enhancer = new Enhancer();
// 设置父类型:
enhancer.setSuperclass(CustomerDao.class);
// 设置回调:
enhancer.setCallback(this);
// 生成代理
CustomerDao customerDaoProxy = (CustomerDao) enhancer.create();
return customerDaoProxy;
}
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
if("delete".equals(method.getName())){
Object obj = methodProxy.invokeSuper(proxy, args);
System.out.println("日志记录================");
return obj;
}
return methodProxy.invokeSuper(proxy, args);
}
}
六、静态代理
代理对象实现目标类接口,并持有目标类对象,在目标类方法前后添加操作