aop简介:
利用aop可以将业务的各个部分分离,实现在不改变源方法的情况下对方法进行增强,aop是动态代理的一个应用
动态代理代码案例:
模拟用户登录,源登录方法只有检查用户名和密码的代码,没有实现检测用户类型的代码。而原方法不可以进行直接修改,这是我们就可以用动态代理实现
1.定义一个接口和实现类,内部有登录方法
为了方便,就不进行数据库操作,直接定义一个用户名和密码
//接口
public interface UserDao {
//用户登录
boolean login(UserData userData);
}
//实现类
public class UserImply implements UserDao {
//模拟数据库的查询结果
private static final String username="admin";
private static final String password="123";
/*
源登陆方法
*/
@Override
public boolean login(UserData userData) {
if(username.equals(userData.getName())&&password.equals(userData.getPassword())){
System.out.println("登录成功");
return true;
}else {
System.out.println("用户名或密码错误,请重试!");
return false;
}
}
}
现在我们来增强login方法,判断用户类型(管理员或普通用户)
定义一个增强方法所在的类,需要实现InvocationHandler接口
class ProxyCommon implements InvocationHandler{
private UserDao userDao;
public ProxyCommon(UserDao userDao){
this.userDao=userDao;
}
/**
*
* @param proxy 代理对象(一般用不到)
* @param method 方法
* @param args 参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//通过反射调用login方法
if("login".equals(method.getName())) {
UserData userData= (UserData) args[0];
boolean invoke = (boolean) method.invoke(userDao, args);
if (invoke){//通过login方法判断是否登陆成功
//登陆成功则判断用户类型
if("管理员".equals(userData.getType())){
System.out.println("您是管理员"+userData.getName()+",将为您跳转到后台管理页面...");
}else {
System.out.println("您好普通用户"+userData.getName());
}
return true;
}else {
return false;
}
}
return null;
}
}
测试:
public static void main(String[] args) {
//首先实例化两个测试用户
UserData commonUser = new UserData("admin","123","普通用户");
UserData adminUser = new UserData("admin","123","管理员");
//实例化需要强化的类,该类的login方法需要强化
UserDao userImply = new UserImply();
/*
使用proxy进行代理,第一个参数是类加载器,第二个参数是类的接口,第三个是代理类
*/
UserDao newUserImpl = (UserDao) Proxy.newProxyInstance(userImply.getClass().getClassLoader(),
userImply.getClass().getInterfaces(), new ProxyCommon(userImply));
//普通用户登录
newUserImpl.login(commonUser);
//管理员登陆
newUserImpl.login(adminUser);
}
运行结果:
有以上运行结果:方法login不再只输出登陆成功或者失败,又增加了判断用户类型的功能,可见方法得到了增强;
spring中的动态代理aop
aop中的术语:
- 连接点:可以被增强的方法称为连接点
- 切入点:实际被增强的方法称为切入点
- 通知(增强):实际增强的逻辑部分称为通知,通知分为五种
1.前置通知
2.后置通知
3.异常通知
4.环绕通知
5.最终通知 - 切面:把通知应用到切入点的过程
切入点表达式
作用:知道哪个类中的那个方法进行增强
语法:
execution(【权限修饰符】【返回值类型】【类的全路径】【方法名】(【参数列表】))
1.建一个需要增强的类
@Component
public class BookImpl{
public void add() {
System.out.println("add方法执行...");
}
}
2.建一个增强类,增强类需要添加@Aspect注解,标注这是个增强类
@Component
@Aspect
public class ProxyBook {
/*
编写切入点表达式,将他放在一个空方法上
后续的通知只需要在注解中标明方法即可将切入点表达式注入
*/
@Pointcut("execution(* com.zhang.aspectj.BookImpl.*(..))")
public void pointcut(){}
//前置通知
@Before("pointcut()")
public void before(){
System.out.println("前置通知执行...");
}
}
在配置文件中开启注解扫描和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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
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">
<context:component-scan base-package="com.zhang"/>
<aop:aspectj-autoproxy/>
</beans>
测试:
@Test
public void aspect(){
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("bean.xml");
BookImpl bookImpl = applicationContext.getBean("bookImpl", BookImpl.class);
bookImpl.add();
}
除了@Before前置通知外还有其他的通知:
- @After:后置通知,方法执行后
- @AfterThrowing:异常通知,方法抛出异常时执行,所以@After和它只能执行一个
- @AfterReturning:最终通知,最后一定执行
- @Around:环绕通知,用法如下:
@Around
@Around("pointcut()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("环绕之前...");
//执行需要增强的方法
joinPoint.proceed();
System.out.println("环绕之后...");
}
最后将所有通知结合测试:
@Component
@Aspect
public class ProxyBook {
/*
编写切入点表达式,将他放在一个空方法上
后续的通知只需要在注解中标明方法即可将切入点表达式注入
*/
@Pointcut("execution(* com.zhang.aspectj.BookImpl.*(..))")
public void pointcut(){}
//前置通知
@Before("pointcut()")
public void before(){
System.out.println("前置通知执行...");
}
//后置通知
@After("pointcut()")
public void after(){
System.out.println("后置通知执行...");
}
//最终通知
@AfterReturning("pointcut()")
public void afterReturning(){
System.out.println("最终通知执行...");
}
//异常通知
@AfterThrowing("pointcut()")
public void afterThrowing(){
System.out.println("异常通知执行...");
}
//环绕通知
@Around("pointcut()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("环绕之前...");
//执行需要增强的方法
joinPoint.proceed();
System.out.println("环绕之后...");
}
}
运行结果:
如果增强类有多个,并且都增强了同一个方法,这是可以在每个增强类上增加@Order注解,里面指定一个数值,数值越小,优先级越高
总结:
使用aop总共有这几步:
- 导入spring-aop,spring-aspects,aspectjwear坐标
- 编写增强类
- 在类上添加@Aspect注解
- 编写切入点表达式
- 编写增强方法,使用注解指明通知类型
- 在xml中开启aop注解支持