一、AOP的描述
在不修改源代码的基础上对类中的方法进行增强,可以实现对同一类方法进行增强。AOP中使用到的名词:
1.连接点:指我们所有的方法
例如:UserService.java这个接口里面的方法就是连接点
public interface UserService {
String selectByName();
@Transactional
int deleteByIdupdate(int[] ids);
int updateByIdupdate();
}
2.切入点:那些需要被增强的方法称为切入点
这就表示我对UserService接口中的任意方法,参数也是任意的进行增强
@Pointcut("execution(* cn.itheima.service.UserService.*(..))")
public void aopLogin() {
}
3.通知:也被成为增强逻辑,我们共性存在的操作。
4.切面:来描述我们"通知"和"切入点"的关系的类。
5.织入:将共性方法还原到切入点的动作
二、AOP思想的实现方式
2.1情况一:有接口类的情况
创建接口实现类代理对象增强类的方法,使用JDK动态代理proxy,而jdk的动态代理只能通过接口实现
2.2情况二:无接口的情况
再没有接口的情况下,AOP需要使用自己内置的CGLIB动态代理,创建当前类子类的代理对象,用来增强类的方法。也就创建一个类(子类)来继承需要增强方法的类(父类)。
2.3需要导入的坐标依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
2.4切入点表达式
1.切入点表达式作用:知道那个类里面的那个方法进行增强
2.语法结构:
execution([ 权限修饰符][返回类型][类全路径][方法名称]([参数列表]))
三、AOP用到的注解
@Aspect:告诉spring扫描到这个注解时,需要进行aop操作,并且扫描以下两个注解:@Pointcut以及通知类型的注解。
@Pointcut:描述标注这是一个切入点
@EnableAspectJAutoProxy:告诉spirng有用注解开发的aop,也就是使spring可以解析@Aspect这个注解,之后@Aspeic才可以识别@Poincut和@Before这两个注解
代码演示:
注解类SpringConfig:
@Component
@ComponentScan("cn.itcast")
@PropertySource("jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
//告诉spirng有用注解开发的aop,也就是使spring可以解析@Aspect这个注解,之后@Aspeic才可以识别Poincut和@Before这两个注解
@EnableAspectJAutoProxy
//开启事务注解管理
@EnableTransactionManagement
public class SpringConfig {
}
通知类,详细信息在注解中,MyAdvice.java:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
@Component//将其放入spring容器中,使其受spring的控制
@Aspect//告诉spring扫描到这个注解时,需要进行aop操作
//这个就是通知类
public class MyAdvice {
// execution()方法,可以写接口也可以写接口的子类,它的根本就是创建类一个父类的子类对象用来加强父类的方法,但是proxy这个jdk的动态代理只能是接口
@Pointcut("execution(* cn.itcast.*.*.married())")
// 方法随便写是一个空壳子,是通知(增强的逻辑)和切入点的链接
private void aaa() {
}
@Pointcut("execution(public int cn.itcast.dao.Man.eat())")
private void bbb() {
}
@Pointcut("execution(public String cn.itcast.service.UserService.find*())")
private void user() {
}
// // 切面,就是将增强逻辑(通知)加入到切入点中,切面是一个动作
@Before("aaa()")//织入的方式,这个befort代表将增强逻辑放在切入点的前方
public void method() {
// 这个方法就是通知,也就是是增强的逻辑
System.out.println(System.currentTimeMillis());
}
// 表示后置操作
@After("aaa()")
public void afterMethod() {
System.out.println("after.....");
}
// 环绕操作
// 环绕操作需要注意的是,原始操作如果有返回值,返回值是可以被取出的
@Around("bbb()")
public Object aroundMethod(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around before");
// 表示对原始操作的调用
Object proceed = pjp.proceed();
System.out.println("around after");
// 如果增强的方法是有返回值的那么需要将返回值得到否则将会爆出异常
return proceed;
}
// 环绕操作对UserMapper中的finAll方法增强
@Around("user()")
public void aroundUser(ProceedingJoinPoint pjp) throws Throwable {
Signature signature = pjp.getSignature();
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
pjp.proceed();
}
long end = System.currentTimeMillis();
System.out.println(signature.getDeclaringTypeName() + "." + signature.getName() + "====>" + (end - start));
}
}
对环绕通知@Around的详解:
环绕通知(重点):@Around,需要依赖形参ProceedingJoinPoint (pjp),如果没有则会对原始方法产生隔离效果
环绕通知可以对:参数进行增强、结果进行增强、前置增强、后置增强、环绕增强
其中对参数进行增强需要:pjp.getArgs()返回一个包含一个方法全部参数的数组,可以用索引取出
对结果进行增强:就是可以将pjp.proceed()执行原方法的结果获取到,然后进行加工后再return
注:signature = pjp.getSignature()方法可以获得签名,通过signature.getName()可以获得方法的名字
四、AOP的工作流程
1.spring容器启动
2.读取所有切面配置中的切入点,也就是说只会读取被织入的切入点
3.初始化bean,判定bean对应的类中的方法是否匹配到任意切入点
匹配失败,创建对象(未被代理的对象,就是源对象)
匹配成功,创建原始对象(目标对象)的代理对象
目标对象:原始功能去掉功能性对应的类产生的对象,这种对象时无法直接完成最终工作的
代理:目标对象无法直接完成工作,需要对去功能回填,通过原始对象的代理对象实现
4.获取bean执行方法
获取bean,调用方法并执行,完成操作
获取bean是代理对象时,根据代理对象的运
五、AOP注解开发思路
1.导入坐标
2.制作连接点方法(原始操作,Dao接口与实现类)
3.制作共性功能(通知类与通知)
4.定义切入点
5.绑定切入点与通知关系
六、完整实现一个用户权限设置
要求:普通用户不可以使用增删改,只能查。使用配置文件模拟,从前端拿到的用户名
实现流程:
1.(Maven)导入依赖:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
2.用来模拟前端数据的properties文件:
login.properties:
login.username=tom
3.创建POJO类User.java无所谓的不写了
4.准备连接点(习惯面向接口)
Userservice.java
import org.springframework.transaction.annotation.Transactional;
public interface UserService {
String selectByName();
@Transactional
int deleteByIdupdate(int[] ids);
int updateByIdupdate();
}
实现类Userserviceimpl.java
import cn.itheima.mapper.UserMapper;
import cn.itheima.pojo.Users;
import cn.itheima.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
public class UserServiceimpl implements UserService {
@Autowired
UserMapper userMapper;
@Override
public String selectByName() {
return null;
}
@Override
public int deleteByIdupdate(int[] ids) {
int count = 0;
for (int i = 0; i < ids.length; i++) {
int i1 = userMapper.deleteById(ids[i]);
int a = 1/0;
count +=i1;
}
return count;
}
@Override
public int updateByIdupdate() {
return 0;
}
}
5.准备通知类
MyAdvice.java:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(* cn.itheima.service.UserService.*(..))")
public void aopLogin() {
}
// 验证用户权限
@Value("${login.username}")
public String username;
@Around("aopLogin()")
public Object aopLoginMethod(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
// 获取签名
Signature signature = pjp.getSignature();
String name = signature.getName();
if (name.contains("update")) {
System.out.println("lsjdlf"+name);
if (username.equals("tom")) {
System.out.println("用户:" + username + "您的权限不够");
}
}
return pjp.proceed();
}
}
6.在注解类中开启AOP扫描:
SpirngConfig.java:
@Component
@ComponentScan("cn.itheima")
@Import({JdbcConfig.class,MybatisConfig.class,})
@PropertySource({"jdbc.properties","login.properties"})
@EnableAspectJAutoProxy//开启AOP注解扫描
public class SpringConfig {
}