主要理念:不用继承,而是用Java Reflect类中的Proxy来实现对方法的增强
面向切面编程 oop的延续,对业务进行隔离,降低耦合度,目标是不改变原来的代码在主干中增加新功能
比如已经写好登录功能,现在需要加入权限,那么应该额外写权限模块,之后配置即可
AOP底层原理:
底层使用动态代理,以下两者差别在于一个接口实现类代理,一个子类实现代理
(1)有接口情况:使用JDK动态代理
比如有UserDao 接口 以及UserDaoImpl实现类
JDK动态代理就是UserDao 接口实现类代理对象,不通过new的方法,以增强类的方法
(2)无接口情况:使用CGLIB动态代理
目标是不改变以下类的add实现方法
public class User{
public void add(){
}
}
原始方法:可以创建User类的子类
新方法:创建User类的子类代理对象,不用new方法
实例
使用JDK动态代理
使用Proxy里面的方法创建代理对象
查阅文档:Java 8 中文版 - 在线API中文手册 - 码工具
java.lang.reflect --》Class Proxy
其中有方法
newProxyInstance(ClassLoader loader, 类<?>[] interfaces, InvocationHandler h)
newProxyInstance(ClassLoader loader, 类<?>[] interfaces, InvocationHandler h)
注意其返回指定接口代理了的实例,静态的方法,可以调用
第一个参数:类加载器 ClassLoader loader
第二个参数:数组,增强方法所在的类,这个类实现的接口,可为多个
第三个参数:实现这个接口InvocationHandler ,创建代理对象 写增强的方法
底层原理代码实现
1)创建接口与实现类
public interface UserDao {
public int add(int a,int b);
public String update(String id);
}
public class UserDaoImpl implements UserDao{
@Override
public int add(int a, int b) {
System.out.println("add方法执行了");
return a+b;
}
@Override
public String update(String id) {
System.out.println("update方法执行了");
return id;
}
}
1)创建JDKProxy类,名字任意
package com.i7i8i9.spring5;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class JDKProxy {
public static void main(String[] args) {
Class[] interfaces={UserDao.class};//为了给Proxy.newProxyInstance提供第二个参数
//创建接口实现类代理对象
UserDaoImpl userDao=new UserDaoImpl(); //这个是为了Proxy.newProxyInstance第三个参数设计的类构造方法
UserDao dao=(UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(),interfaces,new UserDaoProxy(userDao));
int sum=dao.add(3,5);
System.out.println("求和"+sum);
}
}
//创建代理对象
class UserDaoProxy implements InvocationHandler {
//创建谁的代理对象,就要把谁传进来,多种方式
//有参构造 Object obj可以用UserDaoImpl,但是范围会缩小
private Object obj;
public UserDaoProxy(Object obj){
this.obj=obj;
}
//增强的逻辑
@Override
//invoke方法意思是对象一创建,方法就会被调用
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//方法之前
System.out.println("方法之前执行"+method.getName()+"传递的参数"+ Arrays.toString(args));
//被增强方法执行
Object res=method.invoke(obj,args);
//方法之后
System.out.println("方法之后,"+obj);
return res;
}
}
main中 UserDao dao=(UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(),interfaces,new UserDaoProxy(userDao)); 相当于 new 对象
这里面分为两个类,第一个是JDKProxy,命名任意,里面包含main方法
主要是围绕Proxy.newProxyInstance三个参数进行
针对第二个参数定义了一个集合:Class[] interfaces={UserDao.class};
针对第三个参数创建了一个类 class UserDaoProxy implements InvocationHandler
2)class UserDaoProxy
在这个类里面第一个要求是:创建谁的代理对象,就要把谁传进来,多种方式实现
这里使用了构造方法传值,为了增强通用性,使用了Object
private Object obj;
public UserDaoProxy(Object obj){
this.obj=obj;
}
第二部分写了增强逻辑
也就是在被增强方法执行前后加入了新逻辑
注意被增强方法执行处其实可以加一个判断
根据method.getName()进行判断
//增强的逻辑
@Override
//invoke方法意思是对象一创建,方法就会被调用
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//方法之前
System.out.println("方法之前执行"+method.getName()+"传递的参数"+ Arrays.toString(args));
//被增强方法执行
Object res=method.invoke(obj,args);
//方法之后
System.out.println("方法之后,"+obj);
return res;
}
3)执行过程
先看执行结果
a)第一步 准备阶段: 传入三个参数
重点是 UserDao dao=(UserDao) Proxy.newProxyInstance(三个参数)
因为Proxy类中创建了UserDaoImpl userDao并把它传给了第三个参数,即
new UserDaoProxy(userDao)
第三个参数返回了
因为是object,所以在UserDao dao=(UserDao)对其进行强转
b)第二步: 调用方法
int sum=dao.add(3,5);
这个触发了被增强方法执行,并返回了Object 对象 com.i7i8i9.spring5.UserDaoImpl@31cefde0
方法之前执行add传递的参数[3, 5]
add方法执行了
方法之后,com.i7i8i9.spring5.UserDaoImpl@31cefde0
求和8
//被增强方法执行 Object res=method.invoke(obj,args);
invoke执行完毕之后,再执行:
System.out.println("求和"+sum);
另外如果main中执行的是userdaoImpl另外一个方法,那么也会被增强,也就是说这个实现类中所有的方法都被增强了,当如不想增强,可以再invoke实现类中用if+method.getName获取当前是什么方法进入执行操作
AOP术语
1.连接点:
从上面可以看出,假设一个实现类中有4个方法, 那么这四个方法都被增强了,他们就是连接点
2.切入点:实际被真正增强的方法
3.通知(增强):实际增强的逻辑部分,就是上文中的权限判断
通知有多种类型
1)前置通知
2)后置通知
3)环绕通知:前后都执行
4)异常通知
5)最终通知:类似try catch 中的finally,无论有没有异常都会执行
切面:是个动作,把通知应用到切入点的过程
AOP操作(准备)
1.Spring框架一般是基于AspectJ实现AOP操作
AspectJ不是Spring组成部分,是一个单独框架,不依赖Spring
一般把AspectJ和Spring结合起来做AOP操作
两种实现方式
1)基于xml配置文件
2)基于注解方式实现
2.引入AOP操作相关依赖
1) spring-framework中的spring-aspects-5.3.9.jar
还有其他三个
2) sf.cglib.jar
https://mvnrepository.com/artifact/net.sourceforge.cglib/com.springsource.net.sf.cglib/2.2.0
点击files下载,出不来的话换一个网址
Download com.springsource.net.sf.cglib-2.2.0.jar : com.springsource.net « c « Jar File Download
3)aopalliance-1.0
第一个网址比较容易查,但有时候不是最新的
aopalliance-1.0.jar下载及Maven、Gradle引入代码,pom文件及包内class -时代Java
或者
https://mvnrepository.com/artifact/aopalliance/aopalliance/1.0
4)Aspectj
https://mvnrepository.com/artifact/org.aspectj/aspectjweaver/1.9.9.1
切入点表达式
作用:知道对哪个类的哪个方法进行增强
语法结构:execution([权限修饰符][返回类型][类全路径][方法名称]([参数列表]))
举例1: 对 com.i7i8i9.dao.BookDao里面的add方法进行增强
execution(* com.i7i8i9.dao.BookDao.add(..))注意*代表public private都可以 两个点代表方法参数
举例2: 对 com.i7i8i9.dao.BookDao里面的所有方法进行增强
execution(* com.i7i8i9.dao.BookDao.*(..))注意第二个*代表所有方法
举例3: 对 com.i7i8i9.dao包里面的所有类的所有方法进行增强
execution(* com.i7i8i9.dao.*.*(..))注意第二个*代表所有类
编写代码
1.建立类:User
2.创建增强类:UserProxy
3.通知配置5步
1)开启注解扫描
比如使用src下xml
类似bean,引入conte和aop命名空间:各插入一行xmlns 增加两个http(修改其中3处名字)
<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
">
<!--开启扫描-->
<context:component-scan base-package="com.i7i8i9.spring5.aopanno"></context:component-scan>
2)使用注解创建被增强和增强类的对象创建
@Component
public class User {
public void add(){
System.out.println("add.......");
}
}
@Component
public class UserProxy {
public void before(){
System.out.println("before----");
}
}
3)在增强类上增加注解 @Aspect,表示要生成代理对象
@Component
@Aspect
public class UserProxy {
public void before(){
System.out.println("before----");
}
}
4)在Spring配置文件中开启生成代理对象,意思就是开启这个之后就去找@Aspect,找到就生成代理对象
<?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
">
<!--开启扫描-->
<context:component-scan base-package="com.i7i8i9.spring5.aopanno"></context:component-scan>
<!--开启Aspect生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
5)配置通知
在增强类的里面,在作为通知方法上面添加通知类型注解,使用切入点表达式
通知类型注解有5种Before After AfterReturning AfterReturning Around
package com.i7i8i9.spring5.aopanno;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class UserProxy {
//@Before是Aspect注解
@Before(value = "execution(* com.i7i8i9.spring5.aopanno.User.add(..))")
public void before(){
System.out.println("before----");
}
//方法执行之后,也是finally无论有没有异常都会执行
@After(value = "execution(* com.i7i8i9.spring5.aopanno.User.add(..))")
public void after(){
System.out.println("after----");
}
//在返回值之后就执行 有异常就不执行
@AfterReturning(value = "execution(* com.i7i8i9.spring5.aopanno.User.add(..))")
public void afterReturn(){
System.out.println("afterReturn----");
}
@AfterThrowing(value = "execution(* com.i7i8i9.spring5.aopanno.User.add(..))")
public void afterThrow(){
System.out.println("异常通知----");
}
@Around(value = "execution(* com.i7i8i9.spring5.aopanno.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
System.out.println("环绕之前----");
proceedingJoinPoint.proceed();//用于区分执行前后,有异常抛出Throwable
System.out.println("环绕之后----");//有异常就不执行
}
}
测试
public class TestUserAop {
@Test
public void userAopTest(){
ApplicationContext context=new ClassPathXmlApplicationContext("bean1.xml");
User user=context.getBean("user", User.class);
user.add();
}
}
没有异常结果
环绕之前----
before----
add.......
afterReturn----
after----
环绕之后----
制造一个异常
在User中增加:int i=10/0;
package com.i7i8i9.spring5.aopanno;
import org.springframework.stereotype.Component;
@Component
public class User {
public void add(){
int i=10/0;
System.out.println("add.......");
}
}
测试结果
环绕之前----
before----
异常通知----
after----
java.lang.ArithmeticException: / by zero
相同切入点提取
新建一个方法加@Pointcut
其他方法value=该方法
@Pointcut(value = "execution(* com.i7i8i9.spring5.aopanno.User.add(..))")
public void pointDemo(){ }
//@Before是Aspect注解 @Before(value = "pointDemo()")
public void before(){
System.out.println("before----");
}
增强优先级
多个类都对一个方法增强,可以在增强类上加@Order(数字类型值),越小优先级越高
似乎没有太大效果
@Order(13)
public class PersonProxy {
@Before(value = "execution(* com.i7i8i9.spring5.aopanno.User.add(..))")
public void afterReturn(){
System.out.println("另外一个增强----");
}
}
@Order(2)
public class UserProxy {
AspectJ 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: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 id="book" class="com.i7i8i9.spring5.aopxml.Book"></bean>
<bean id="bookProxy" class="com.i7i8i9.spring5.aopxml.BookProxy"></bean>
<!-- 切入点配置-->
<aop:config>
<!-- 切入点 p1名字随便起-->
<aop:pointcut id="p1" expression="execution(* com.i7i8i9.spring5.aopxml.Book.buy(..))"/>
<!-- 配置切面-->
<aop:aspect ref="bookProxy">
<!-- 配置增强在具体方法上-->
<aop:before method="before" pointcut-ref="p1" />
</aop:aspect>
</aop:config>
</beans>
全注解方式
即不用xml
创建一个配置类
@Configuration
@ComponentScan(basePackages = {"com.i7i8i9"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class ConfigAop {
}