文章目录
前言
Spring的两大核心是IOC和AOP,其中IOC是指控制反转,指的是bean对象无需用户手动维护,全部交由Spring管理;AOP大家都知道是面向切面,但是什么是面向切面,这里面由涉及的一些名词如何理解,我们如何理解并使用AOP,在这里我们简单聊一下
一、AOP是什么?
AOP(Aspect-Oriented Programming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需 要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日 志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种 散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为 “Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低 模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为; 那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手 将这些剖开的切面复原,不留痕迹。
二、设计模式
IOC是由Spring管理,默认是单例模式;AOP使用的是代理模式,将业务通过代理类来附加一些额外操作,避免了额外操作直接增加到业务上,对业务造成耦合冗余。
上面的说明可能有点晦涩难懂,举个例子来说,我们想要租赁房子,房东想要出租房子,但是租房子不定时的会有人来看房子,需要看房、签合同、交钱、整理,房东觉得麻烦,就将出租房子的权利由房产中介代理,这样方式就是代理模式。代理模式分为静态代理和动态代理,我们这里不具体展开来讲,后续有机会会出一个两个的具体说明。
代理模式,我们可以简单的理解为4个类:
- 业务接口——租房,一般用抽象类或者接口来解决
- 真实角色——房东,被代理的角色
- 代理角色——中介,代理真实角色去操作,并可以附加其他操作
- 客户端——访问代理角色的人
图示:
流程分析
- 客户想租房,必须直接与房东打交道。
- 房东委托中介作为代理,代替房东来租房-----------此时 代理角色和真实角色都拥有了租房功能
- 中介除了代理房东来租房,还会进行一些代理房东租房之外的操作,比如看房、收中介费等
- 然后客户就可以通过与代理[中介]打交道,来租房
三、名词解释
横切关注点:我们要在业务逻辑上增加的共有功能,如日志、安全、缓存、事务
切面Aspect:横切关注点 模块化的实例类
通知Advice: 切面的方法
目标Target: 要实现的功能,接口或者抽象类,如:租房
代理Proxy: 代理角色,如中介
切入点CutPoint:切面的方法执行的“地点”
连接点JoinPoint:与切入点匹配的执行点
图示:
四、代码
方式一
前置加强类(ex:BeforeLog)实现前置加强接口(implements MethodBeforeAdvice)
后置加强类(ex:AfterLog)实现后置加强接口(implements AfterReturningAdvice )
配置xml,由于前/后置加强类实现了相应的前/后置加强接口,所以无需再定义切面<aop:aspect ...></aop:aspect>
<bean id="userService" class="com.jd.service.UserServiceImpl"/>
<bean id="beforeLog" class="com.jd.log.BeforeLog"/>
<bean id="afterLog" class="com.jd.log.AfterLog"/>
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.jd.service.UserServiceImpl.*(..))"/>
<aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
public interface UserService {
public int add(User user);
public void remove();
public User modify(User user);
public void query();
}
public class UserServiceImpl implements UserService{
@Override
public int add(User user) {
System.out.println("=====增加元素=====");
//todo 连接DB,插入数据,返回
return 1;
}
@Override
public void remove() {
System.out.println("=====删除元素=====");
}
@Override
public User modify(User user) {
System.out.println("=====修改元素=====");
user.setAge(18);
user.setFavourites(Arrays.asList("study"));
return user;
}
@Override
public void query() {
System.out.println("=====查询元素=====");
}
}
public class BeforeLog implements MethodBeforeAdvice {
/**
*
* @param method 要执行的具体业务方法
* @param objects 参数
* @param target 要执行的业务类实例对象
* @throws Throwable
*/
@Override
public void before(Method method, Object[] objects, Object target) throws Throwable {
System.out.println("前置加强:"+target.getClass().getName() + "的" + method.getName() + "方法被执行了");
Arrays.stream(objects).forEach(obj->{
User user = (User) obj;
System.out.println("前置加强获取请求参数:"+obj.toString());
});
}
}
public class AfterLog implements AfterReturningAdvice {
@Override
public void afterReturning(Object result, Method method, Object[] args, Object target) throws Throwable {
System.out.println("后置加强:"+target.getClass().getName() + "的" + method.getName() + "被执行了");
System.out.println("后置加强 返回结果是:"+result);
}
}
public class MyStart {
public static void main(String[] args) {
ApplicationContext cpx = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = (UserService) cpx.getBean("userService");
User user = new User();
user.setName("zhangsan");
user.setAge(15);
user.setSex(1);
user.setFavourites(Arrays.asList("eat","drink","play"));
//userService.add(user);
userService.modify(user);
}
}
结果:
前置加强:com.jd.service.UserServiceImpl的modify方法被执行了
前置加强获取请求参数:User{name='zhangsan', age=15, sex=1, favourites=[eat, drink, play]}
=====修改元素=====
后置加强:com.jd.service.UserServiceImpl的modify被执行了
后置加强 返回结果是:User{name='zhangsan', age=18, sex=1, favourites=[study]}
方式二
定义切面类
配置文件xml:定义切面,指向切面类;定义切点以及切点位置前后执行的通知
<bean id="userService" class="com.jd.service.UserServiceImpl"/>
<bean id="log" class="com.jd.log.Log"/>
<aop:config>
<!--自定义切面,ref指向切面类-->
<aop:aspect ref="log">
<!--切入点-->
<aop:pointcut id="pointcut" expression="execution(* com.jd.service.UserServiceImpl.*(..))" />
<!--前置后置通知,意思是 在切点位置,前面执行前置通知,后面执行后置通知-->
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
public interface UserService {
public int add(User user);
public void remove();
public User modify(User user);
public void query();
}
public class UserServiceImpl implements UserService{
@Override
public int add(User user) {
System.out.println("=====增加元素=====");
//todo 连接DB,插入数据,返回
return 1;
}
@Override
public void remove() {
System.out.println("=====删除元素=====");
}
@Override
public User modify(User user) {
System.out.println("=====修改元素=====");
user.setAge(18);
user.setFavourites(Arrays.asList("study"));
return user;
}
@Override
public void query() {
System.out.println("=====查询元素=====");
}
}
/**
* 切面类
*/
public class Log {
public void before(){
System.out.println("===执行前置加强==");
}
public void after(){
System.out.println("===执行后置加强==");
}
}
public class MyStart {
public static void main(String[] args) {
ApplicationContext cpx = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = (UserService) cpx.getBean("userService");
User user = new User();
user.setName("zhangsan");
user.setAge(15);
user.setSex(1);
user.setFavourites(Arrays.asList("eat","drink","play"));
//userService.add(user);
userService.modify(user);
}
}
结果:
===执行前置加强==
=====修改元素=====
===执行后置加强==
方式三:注解
定义切面类,使用注解标识前置advice、后置advice、环绕advice
配置xml 开启aop注解
<bean id="annotation" class="com.jd.log.AnnotationpointCut"/>
<aop:aspectj-autoproxy/>
@Aspect
public class AnnotationpointCut {
@Before("execution(* com.jd.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("==方法执行前=");
}
@After("execution(* com.jd.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("==方法执行后=");
}
@Around("execution(* com.jd.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("==环绕前=");
joinPoint.proceed();
System.out.println("==环绕后=");
}
}
public class MyStart {
public static void main(String[] args) {
ApplicationContext cpx = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = (UserService) cpx.getBean("userService");
User user = new User();
user.setName("zhangsan");
user.setAge(15);
user.setSex(1);
user.setFavourites(Arrays.asList("eat","drink","play"));
//userService.add(user);
userService.modify(user);
}
}
结果:
==环绕前=
==方法执行前=
=====修改元素=====
==环绕后=
==方法执行后=
总结
AOP是Spring中非常重要的一部分,在生产中会经常用到,我们要理解其中代理模式的含义才能更行之有效的让我们的代码逻辑更精炼。
3种实现AOP的方式,第一种能在通知Advice中获取方法、参数、响应,但是需要我们实现java接口,在实际开发中可用性不大;
第二种使用配置的方式,在xml配置中能比较好操作
第三种使用注解,节省了xml配置,但是在切面类中每个增强方法都要写execution(),比较复杂
最终推荐第二种,另外两种酌情使用