Spring5-AOP
# Spring5-AOP
# 1. 面向切(方)面变成概念
# 1.1 AOP概念
AOP:Aspect Oriented Programming(面向切面编程或面向方面编程),是一种编程范式,提供从另一个角度来考虑程序结构从而完善面向对象编程(OOP)。
AOP为开发者提供一种进行横切关注点(比如日志关注点横切了支付关注点)分离并织入的机制,把横切关注点分离,然后通过某种技术织入到系统中,从而无耦合的完成了我们的功能。
面向切面的编程和面向对象并不矛盾,是对面向对象的思维方式的有效补充。主要将程序中设计公共问题集中解决,可以解决面向对象和过成化方法中不能很好解决的横切问题,如:事务,安全,日志,异常处理等横切关注。
# 1.2 利用代理实现面向切面编程
代理:
代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类(小王)负责为委托类(小明)预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。
代理模式是常用的Java 设计模式,它的特征是代理类与委托类有同样的接口(小明对小王说,你帮我传个小纸条给小花),代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。按照代理类的创建时期,代理类可分为两种。
# 2. 关于静态代理和动态代理
# 2.1 静态代理类
由程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。动态代理类:在程序运行时,运用反射机制动态创建而成。
华哥想追求隔壁的班花,自己又不认识,所以请一个代理(小明)帮忙:设计代码如下:
/**
* 定义一个班花
* @author Administrator
*
*/
public class ClassFlower {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ClassFlower(String name) {
this.name = name;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
定义追求班花的方法:
/**
* 定义的接口
*
* @author Administrator
*
*/
public interface IGiveSomething {
void giveMoney();
// void giveCar();
void giveSeaHouse();
}
2
3
4
5
6
7
8
9
10
11
12
13
实现接口的方法:
/**
* SuperMan 符合大家口味 追求者
*
* @author Administrator
*
*/
public class SuperMan implements IGiveSomething {
private ClassFlower cf;
public ClassFlower getCf() {
return cf;
}
public void setCf(ClassFlower cf) {
this.cf = cf;
}
public SuperMan(ClassFlower cf) {
this.cf = cf;
}
@Override
public void giveSeaHouse() {
System.out.println("华哥 送给女生" + cf.getName() + " 一栋海景房~~~");
}
@Override
public void giveMoney() {
System.out.println("华哥 送给女生" + cf.getName() + " 100亿~~~");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
代理类的具体实现:
public class Proxy implements IGiveSomething {
private SuperMan superman;
public SuperMan getSuperman() {
return superman;
}
public void setSuperman(SuperMan superman) {
this.superman = superman;
}
// 我们做华哥的代理
public Proxy(SuperMan sm) {
this.superman = sm;
}
@Override
public void giveSeaHouse() {
System.out.println("这是在调用华哥的方法之前完成的事情");
// 此处虽然实现 giveSeaHouse这个方法 实际上市帮华哥做这件事情
// 要调用的是 华哥对象的方法
superman.giveSeaHouse();
System.out.println("这是在调用华哥的方法之后完成的事情");
}
@Override
public void giveMoney() {
superman.giveMoney();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
测试类:
/**
* 测试类
* @author Administrator
*
*/
public class TestHuaGe {
public static void main(String[] args) {
// 指定华哥心中的女神
ClassFlower cf = new ClassFlower("如花");
// 创建一个华哥对象
SuperMan sm = new SuperMan(cf);
// 创建一个代理对象
Proxy proxy = new Proxy(sm);
proxy.giveSeaHouse();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
测试结果:我们可以在华哥的方法之前或者之后加入一些操作,如日志记录等:
这是在调用华哥的方法之前完成的事情
华哥 送给女生如花 一栋海景房~~~
这是在调用华哥的方法之后完成的事情
2
3
# 2.1.1 静态代理类优缺点
优点:业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。 缺点: 1)代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。 2)如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
RealSubject:真实角色,是实现抽象接口的类。
Proxy:代理角色,内部含有对真实对象RealSubject的引用,从而可以操作真实对象。代理对象提供与真实对象相同的接口,以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
Subject : 接口,是对象和它的代理共用的接口,让RealSubject和Proxy具有一致性。
# 2.2 动态代理类:
源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。
/**
* 自定一个动态的代理类
*
* @author Administrator
*
*/
public class ProxyDy implements InvocationHandler {
// 代理的是华哥 也可能是其他的对象等等...
private Object obj;
public Object getObj() {
return obj;
}
public void setObj(Object obj) {
this.obj = obj;
}
// 构造 当实例化该代理对象 直接得到其要代理具体对象是谁
public ProxyDy(Object obj) {
// TODO Auto-generated constructor stub
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
System.out.println("动态代理 方法前的动作 ");
// 调用的那个代理对象的方法
Object result = method.invoke(obj, args);
System.out.println("动态代理 方法后的动作 ");
return result;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
在客户端:
public class TestProxyDy {
/**
* @param args
*/
public static void main(String[] args) {
// 班花
ClassFlower cf = new ClassFlower("聪志姐姐");
// 华哥
SuperMan sm = new SuperMan(cf);
// 得到一个动态代理类对象 ProxyDy类的对象
ProxyDy pdy = new ProxyDy(sm);
//第一个参数 是pdy对象对应的类的加载器 pdy定义的那个动态代理类的实例
//第二个参数要代理的那个对象,也就是借口的实现类 对应的对象接口
//第三个参数是 定义那个实现了Handler接口的类对象,既ProxyDy对象pdy
IGiveSomething igs = (IGiveSomething) Proxy.newProxyInstance(pdy
.getClass().getClassLoader(), sm.getClass().getInterfaces(),
pdy);
igs.giveSeaHouse();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
测试结果:
动态代理 方法前的动作
华哥 送给女生聪志姐姐 一栋海景房~~~
动态代理 方法后的动作
2
3
# 2.2.1 优点
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理 (InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进 行中转。在本示例中看不出来,因为invoke方法体内嵌入了具体的外围业务(记录任务处理前后时间并计算时间差),实际中可以类似Spring AOP那样配置外围业务。
美中不足: 诚然,Proxy 已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持 interface 代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫 Proxy。Java 的继承机制注定了这些动态代理类们无法实现对 class 的动态代理,原因是多继承在 Java 中本质上就行不通。
在程序中的某个操作逻辑之前和之后需要加入一些日志记录。如:记录什么时间,谁在那里做了什么事情. 如果通过动态代理该如何实现呢?
UserDao参考代码:
public interface UserDao {
public void save(User u);
}
2
3
UserDaoImpl实现类:
public class UserDaoImpl implements UserDao {
@Override
public void save(User u) {System.out.println("user save...");}
}
2
3
4
UserService类:
public class UserService {
private UserDao userDao = new UserDaoImpl();
public UserDao getUserDao() {return userDao;}
public void setUserDao(UserDao userDao) {this.userDao = userDao;}
public void add(User u){ userDao.save(u);}
}
2
3
4
5
6
User类(Model):
public class User {
private String username;
private String password;
//封装省略
}
2
3
4
5
Spring的配置文件
<beans>
<bean id="u" class="com.etc.dao.impl.UserDaoImpl"/>
<bean id="userService" class="com.etc.service.UserService">
<!-- <property name="userDao" bean="u"/> -->
</bean>
</beans>
2
3
4
5
6
需要处理的日志类(实现了InvocationHandler接口),只有实现了InvocationHandler接口的类才可以进行动态代理
public class LogIntroduction implements InvocationHandler {
private Object target;
public LogIntroduction(UserDao userDao) {this.target = userDao;}
//此beforeMethod就是在执行每个方法之前需要加载的日志方法。
public void beforeMethod(){System.out.println("beforMethod");}
@Override
public Object invoke(Object arg0, Method m, Object[] arg)
throws Throwable {
this.beforeMethod();
m.invoke(target, arg);
return null;
}}
2
3
4
5
6
7
8
9
10
11
12
13
测试类
public void testAdd_3() throws Exception {
ClassPathXmlApplicationContext factory = new ClassPathXmlApplicationContext();
UserService service = (UserService)factory.getBean("userService");
UserDao userDao = (UserDao)factory.getBean("u");
LogIntroduction li = new LogIntroduction(userDao);
UserDao userDaoProxy =(UserDao)Proxy.newProxyInstance(userDao.getClass().getClassLoader(), new Class[]{UserDao.class}, li);
User u = new User();
service.setUserDao(userDaoProxy);
service.add(u);
2
3
4
5
6
7
8
9
结果
beforMethod
user save...
2
这样就在执行每个方法之前加入日志处理程序了。就是利用JAVA的动态代理实现的。
# 3. AOP相关概念
# 3.1 方面/切面(Aspect)
其实就是共有功能的实现。如日志切面、权限切面、事务切面等。在实际应用中通常是一个存放共有功能实现的普通Java类,之所以能被AOP容器识别成切面,是在配置中指定的。可以认为是通知、引入和切入点的组合;在Spring中可以使用Schema和@AspectJ方式进行组织实现;
# 3.2 通知(Advice)
是切面的具体实现,在连接点上执行的行为,通知提供了在AOP中需要在切入点所选择的连接点处进行扩展现有行为的手段。以目标方法为参照点,根据放置的地方不同,可分为前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)与环绕通知(Around)5种。在实际应用中通常是切面类中的一个方法,具体属于哪类通知,同样是在配置中指定的。做什么?
# 3.3 连接点(Joinpoint)
表示需要在程序中插入横切关注点的扩展点,连接点可能是类初始化、方法执行、方法调用、字段调用或处理异常等等. 在哪里(很多可以连接的部分)做什么?
# 3.4 切入点
选择一组相关连接点的模式,即可以认为连接点的集合,Spring支持perl5正则表达式和AspectJ切入点模式,Spring默认使用AspectJ语法,在AOP中表示为“在哪里(确定下来)做的集合”.
# 3.5 目标对象(TargetObject)
需要被织入横切关注点的对象,即该对象是切入点选择的对象,需要被通知的对象,从而也可称为“被通知对象”;由于Spring AOP 通过代理模式实现,从而这个对象永远是被代理对象.对谁做这件事?
# 3.6 代理对象(AOP Proxy Object)
AOP框架使用代理模式创建的对象,从而实现在连接点处插入通知(即应用切面),就是通过代理来对目标对象应用切面。在Spring中,AOP代理可以用**JDK动态代理【返回接口实例】或CGLIB[可以是类实例]**代理实现,而通过拦截器模型应用切面。
# 3.7 织入(Weaving)
将切面应用到目标对象从而创建一个新的代理对象的过程。这个过程可以发生在编译期、类装载期及运行期,当然不同的发生点有着不同的前提条件。譬如发生在编译期的话,就要求有一个支持这种AOP实现的特殊编译器;发生在类装载期,就要求有一个支持AOP实现的特殊类装载器;只有发生在运行期,则可直接通过Java语言的反射机制与动态代理机制来动态实现。
# 3.8 通知类型
前置通知(Before Advice):在切入点选择的连接点处的方法之前执行的通知,该通知不影响正常程序执行流程(除非该通知抛出异常,该异常将中断当前方法链的执行而返回)。
后置通知(After Advice): 在切入点选择的连接点处的方法之后执行的通知,包括如下类型的后置通知:
后置返回通知(After returning Advice):在切入点选择的连接点处的方法正常执行完毕时执行的通知,必须是连接点处的方法没抛出任何异常正常返回时才调用后置通知。
后置异常通知(After throwing Advice): 在切入点选择的连接点处的方法抛出异常返回时执行的通知,必须是连接点处的方法抛出任何异常返回时才调用异常通知。
后置最终通知(After finally Advice): 在切入点选择的连接点处的方法返回时执行的通知,不管抛没抛出异常都执行,类似于Java中的finally块。
环绕通知(Around Advices):环绕着在切入点选择的连接点处的方法所执行的通知,环绕通知可以在方法调用之前和之后自定义任何行为,并且可以决定是否执行连接点处的方法、替换返回值、抛出异常等.
# 4. 基于XML的SpringAOP配置
xml方式是我们以后使用的比较多的,因为当切面类我们没有源代码时、当我们使用第三方的切面类时,我就不能使用annotation的方式,而且如果使用annotation方式一但程序编译后就不可以修改了。如果使用xml方式就不一样了,我们只需要修改xml文件就可以。
# 4.1 操作步骤
导入外部类Spring5.1 使用
aopaliance-1.0.jar
aspectjweaver-1.8.9.jar
定义目标接口
/** * @Author: Intheory * @Date: 2021/8/16 10:26 * @Description: //TODO 定义目标接口 */ public interface IGiveMessaga { public void sendMessage(); }
1
2
3
4
5
6
7
8定义目标实现类
/** * @Author: Intheory * @Date: 2021/8/16 10:27 * @Description: //TODO 目标实现类 */ public class MessageService implements IGiveMessaga{ @Override public void sendMessage() { System.out.println("晚上8点,老地方见!"); } }
1
2
3
4
5
6
7
8
9
10
11定义切面的支持类
/** * @Author: Intheory * @Date: 2021/8/16 9:53 * @Description: //TODO 切面支持类 */ public class LogAOP { //前置通知 public void before(){ System.out.println("before:" + System.currentTimeMillis()); } //后置通知 public void after(){ System.out.println("after:" + System.currentTimeMillis()); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15在applicationContext.xml中配置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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 创建MessageService bean--> <bean class="com.etc.aop.MessageService" id="messageService"> </bean> <!-- 创建bean--> <bean class="com.etc.aop.LogAOP" id="logAop"> </bean> <!-- aop配置--> <aop:config> <!-- 定义切入点--> <aop:pointcut id="pointcut" expression="execution(* com.etc.aop.MessageService.sendMessage(..))"/> <!-- 定义切面对象--> <aop:aspect ref="logAop"> <aop:before method="before" pointcut-ref="pointcut"/> <aop:after method="after" pointcut-ref="pointcut"/> </aop:aspect> </aop:config> </beans>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26Junit测试代码
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:Spring_aop.xml"}) public class TestAOP { @Autowired //MessageService 类-》默认使用的是jdk动态代理,只能处理接口 private IGiveMessaga ms; @Test public void testBlog(){ ms.sendMessage(); } }
1
2
3
4
5
6
7
8
9
10
11输出结果
before:1629080117699 晚上8点,老地方见! after:1629080117700
1
2
3
在Spring配置文件中,所以AOP相关定义必须放在aop:config标签下,该标签下可以有aop:pointcut、aop:advisor、aop:aspect标签,配置顺序不可变。
- aop:pointcut:用来定义切入点,该切入点可以重用;
- aop:advisor:用来定义只有一个通知和一个切入点的切面;
- aop:aspect:用来定义切面,该切面可以包含多个切入点和通知,而且标签内部的通知和切入点定义是无序的;和advisor的区别就在此,advisor只包含一个通知和一个切入点。
需要带参数: ProceedingJoinPoint
# 4.1.1 环绕通知的方法
//环绕通知的方法
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable
{
System.out.println("around before advice~~~");
//Object val=pjp.proceed(new Object[]{"aroundAdvice~~~"});
Object val=pjp.proceed();
System.out.println("around after advice~~~");
return val;
}
2
3
4
5
6
7
8
9
10
aop的配置信息
<!-- aop的配置信息 -->
<aop:config>
<!-- 配置切入点 expression 表达式 同时给切入点指定一个标识 id-->
<aop:pointcut expression="execution(* com.etc.service.*.showInfo(..))"
id="mypointcut" />
<!-- 指定切面的引用 -->
<aop:aspect ref="ls">
<!-- 指定前后的执行方法 -->
<aop:before method="before" pointcut-ref="mypointcut" />
<aop:after method="after" pointcut-ref="mypointcut" />
<aop:around method="around" pointcut-ref="mypointcut" />
</aop:aspect>
</aop:config>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 5. 基于注解的AOP
Spring除了支持Schema(XML)方式配置AOP,还支持注解方式:使用@AspectJ风格的切面声明。Spring默认不支持@AspectJ风格的切面声明,为了支持需要使用如下配置:
- 在Spring全局的配置文件中需要生命autoproxy
<!-- 支持aop注解 需要告知spring 此时我们使用注解配置了和aop有关的@Aspect @Before @After等等-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
2
- 在切面类上增加注解,在切面类的方法增加通知类型以及切入点的语法
@Aspect
@Component
public class LogAop {
@Before(value="execution(* com.etc.service.*.*(..))")
public void before() {
String now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
System.out.println("begin: "+now);
PrintLog.writeFile("begin: "+now);//输出到文件
}
//@After(value="execution(* com.etc.service.*.*(..))")
@After(value ="execution(* com.xmvpd.service.*.*(..)")
public void after() {
String now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
System.out.println("end : "+now);
PrintLog.writeFile("end : "+now);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:springAnnotation.xml"})
public class TestUsers {
@Autowired
private UsersService usersService;
@Test
public void testUsers(){
System.out.println(usersService.getUsers());
}
}
2
3
4
5
6
7
8
9
10
11
12
# 5.1 AOP的annotation实例
要求:在执行sendMessage()方法之前加入日志逻辑
/**
* 切面类
* @author knowno
*
*/
@Aspect
public class LogAOP {
// 前置通知的方法
@Before(value="execution (public void com.etc.service.impl.MessageService.sendMessage())")
public void beforeAdvice() {
System.out.println("Log AOP before advice~~~");
}
// 后置通知的方法
@After(value="execution (public void com.etc.service.impl.MessageService.sendMessage())")
public void afterAdvice() {
System.out.println("Log AOP after advice~~~");
}
// 环绕通知的方法
@Around(value="execution (public void com.etc.service.impl.MessageService.sendMessage())")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("Log AOP around before advice~~~");
// Object val=pjp.proceed(new Object[]{"aroundAdvice~~~"});
Object val = pjp.proceed();
System.out.println("Log AOP around after advice~~~");
return val;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
sendMessage()方法之前就会先执行这个逻辑了。其中,@Aspect:是定义这个类为切面类,@Componet:因为作为切面类需要Spring管理起来,所以在初始化时就需要将这个类初始化加入Spring的管理;@Befoe:切入点的逻辑(Advice);execution…:切入点语法
applicationContext.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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<aop:aspectj-autoproxy /> <!—这句要加 自动-->
<!-- 目标类 -->
<bean id="ms" class="com.etc.service.impl.MessageService"></bean>
<bean id="sm" class="com.etc.aop.SendMessageAOP"></bean>
<bean id="la" class="com.etc.aop.LogAOP"></bean>
</beans>
2
3
4
5
6
7
8
9
10
11
12
13
14
# 5.2 @Pointcut的用法
当多个Advice个有相同的织入点。那么我们可以定义一个织入点集合,在需要使用的地方,调用就可以了。
例如:
@Aspect
@Component
public class LogInterceptor {
//空方法 只是希望其他地方可以调用而已
@Pointcut("execution(public * com.etc.dao.*.*(..))")
public void myMethod(){};
@Before(value="myMethod()")
public void before(){
System.out.println("method start...");
}
@AfterReturning("myMethod()")
public void afterReturning(){
System.out.println("method after returning...");
}}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 5.3 annotation方式的AOP实例
@Aspect
@Component
public class LogInterceptor {
@Pointcut("execution(public * com.etc.dao..*.*(..))")
public void myMethod(){};
@Before(value="myMethod()")
public void before(){
System.out.println("method start...");
}
@AfterReturning("myMethod()")
public void afterReturning(){
System.out.println("method after returning...");
}
@Around(value="myMethod()")
public void around(ProceedingJoinPoint pjp) throws Throwable{
//因为@around需要传入一个参数ProceedingJoinPoint进行前后加逻辑
System.out.println("method around start...");
//在需要前后逻辑的中间加入下列语句。表示前后逻辑,可能会抛出异常Throwable。
pjp.proceed();
System.out.println("method around end...");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 6. AOP小结
1、关于概念的理解 :切面,切入点,连接点,通知,目标对象等
2、常见通知的类型有几种?
3、AOP的理解?以及实现?
4、设计模式之: 代理模式. 静态代理和动态代理的区别?
5、使用xml如何定义切面?切点?
6、使用注解如何定义切面?前置通知?后置通知?