AOP面向切面编程
一、面向切面编程概念
将切面提取出来单独开发,在需要调用的方法中通过动态代理的方式进行植入
切面:公共的 通用的 重复的功能
二、手写AOP框架
业务:图书购买业务 ,切面:事务
1.业务和切面紧耦合在一起,没有拆分
/**
* 图书购买业务和事务的切面
*/
public class BookServiceImpl {
public void buy(){
try{
System.out.println("事务开启.....");
System.out.println("图书购买业务功能实现.....");
System.out.println("事务提交....");
}catch (Exception e){
System.out.println("事务回滚....");
}
}
}
2.使用子类代理的方式拆分业务和切面
/**
* 使用子类代理的方式进行图书业务和事务切面的拆分
*/
public class BookServiceImpl {
//只有业务
public void buy(){
System.out.println("图书购买功能实现....");
}
}
/**
* 子类是代理类,将父类的图书购买功能添加事务切面
*/
public class SubBookServiceImpl extends BookServiceImpl{
@Override
public void buy() {
try{
//事务切面
System.out.println("事务开启...");
//主业务实现
super.buy();
//事务切面
System.out.println("事务提交....");
}catch (Exception e){
System.out.println("事务回滚....");
}
}
}
3.使用静态代理拆分业务和切面
业务和业务已经拆分,但是切面紧耦合在业务中
public interface Service {
//规定业务功能
void buy();
}
public class ProductServiceImpl implements Service{
@Override
public void buy() {
System.out.println("商品购买业务实现....");
}
}
public class BookServiceImpl implements Service{
@Override
public void buy() {
System.out.println("图书购买业务功能实现....");
}
}
/**
* 静态代理已经实现了目标对象的灵活切换
*/
public class Agent implements Service{
//设计成员变量的类型为接口,为了灵活切换目标对象
public Service target;
//使用构造方法传入目标对象
public Agent(Service target){
this.target=target;
}
@Override
public void buy() {
try{
//切面功能
System.out.println("事务开启....");
//业务功能
target.buy();
//切面功能
System.out.println("事务提交....");
}catch (Exception e){
System.out.println("事务回滚....");
}
}
}
4.使用静态代理拆分业务和业务接口,切面和切面接口
public interface AOP {
default void before(){}
default void after(){}
default void exception(){}
}
public class LogAop implements AOP{
@Override
public void before() {
System.out.println("前置日志输出.....");
}
}
public class TransAop implements AOP{
@Override
public void before() {
System.out.println("事务开启......");
}
@Override
public void after() {
System.out.println("事务提交.....");
}
@Override
public void exception() {
System.out.println("事务回滚");
}
}
public interface Service {
//规定业务功能
void buy();
}
public class ProductServiceImpl implements Service {
@Override
public void buy() {
System.out.println("商品购买业务实现....");
}
}
public class BookServiceImpl implements Service {
@Override
public void buy() {
System.out.println("图书购买业务功能实现....");
}
}
public class Agent implements Service{
//传入目标业务对象,切面对象
Service target;
AOP aop;
//使用构造方法初始化业务对象和切面对象
public Agent(Service target,AOP aop){
this.target=target;
this.aop=aop;
}
@Override
public void buy() {
try{
//切面 事务/日志
aop.before();
//业务 图书。商品
target.buy();
//切面 事务
aop.after();
}catch (Exception e){
//切面
aop.exception();
}
}
}
5.使用动态代理完成4的优化
public interface AOP {
default void before(){}
default void after(){}
default void exception(){}
}
public class LogAop implements AOP{
@Override
public void before() {
System.out.println("前置日志输出.....");
}
}
public class TransAop implements AOP{
@Override
public void before() {
System.out.println("事务开启......");
}
@Override
public void after() {
System.out.println("事务提交.....");
}
@Override
public void exception() {
System.out.println("事务回滚");
}
}
public interface Service {
//规定业务功能
void buy();
}
public class ProductServiceImpl implements Service {
@Override
public void buy() {
System.out.println("商品购买业务实现....");
}
}
public class BookServiceImpl implements Service {
@Override
public void buy() {
System.out.println("图书购买业务功能实现....");
}
}
public class ProxyFactory {
public static Object getAgent(Service target,AOP aop){
/**
* ClassLoader 第一个参数:类加载器
* Class<?>[] interfaces 第二个参数:目标对象实现的所有接口
* reflect.InvocationHandler 第三个参数:代理功能实现
* 返回:生成的代理对象
*/
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
/**
*proxy 生成的代理对象
*method 正在被调用的目标方法
*args 目标方法的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object obj = null;
try {
//切面 事务/业务
aop.before();
//业务
obj = method.invoke(target, args);
//切面
aop.after();
} catch (Exception e) {
//切面
aop.exception();
}
//切面
return obj;//目标方法的返回值
}
}
);
}
}
三、Spring原生AOP
四、AOP常见术语
(1)切面:那些重复的、公共的、通用的功能,例如:日志 事务 权限
(2)连接点:就是目标方法,因为在目标方法中要实现目标方法的功能和切面功能
(3)切入点(Pointcut):多个连接点构成切入点,切入点可以是一个目标方法,可以是一个类中的所有方法,可以是一个包下的所有方法
(4)目标对象:操作谁谁就是目标对象
(5)通知(Advice):来指定切入的时机,是在目标方法执行前 还是执行后 还是出错时 还是环绕目标方法切入面功能
五、AspectJ框架
1.什么是AspectJ框架
是一个优秀的面向切面的框架,它扩展了java语言,提供了强大的切面实现,是由java实现的
2.AspectJ常见的通知类型
(1)前置通知 @Before
(2)后置通知 @AfterReturing
(3)环绕通知 @Around 常用于事务,功能最强大
(4)最终通知 @After
(5)定义切入点(了解) @Pointcut
3.AspectJ的切入点表达式(掌握)
(1)公式和符号
(1)
规范公式:
execution(访问权限 方法返回值 方法声明(参数) 异常类型)
简化:
execution(方法返回值 方法声明(参数))
(2)用到的符号
* 代表人一个任意的字符(通配符)
.. 如果出现在方法的参数中则代表任意参数,如果出现在路径中则代表本路径及其所有的子路径
(2)示例
(1)execution(public * *(..))
公共访问权限下的 任意返回值的 任意路径下的 任意参数的方法 ---> 公共访问权限下的所有方法
(2)execution(* set*(..))
任意返回值类型的 任意路径下的 以set打头的所有方法 ---> 任意一个以"set"开始的方法
(3)execution(* com.xyz.service.impl.*.*(..))
任意返回值类型的,在com.xyz.service.impl包下的任意类的任意方法的任意参数
(3)execution(* com.xyz.service..*.*(..))
任意返回值类型的,在 com.xyz.service及其子包下的任意类的任意方法的任意参数
(4)execution(* *..service.*.*(..))
任意返回值类型的,(service之前可以有任意的包),指定所有包下的service子包下所有类中所有的方法作为切入点
4.前置通知 @Before
在目标方法执行前切入切面功能,在切面方法中不可以获得目标方法的返回值,只能得到目标方法的签名。
签名:访问权限 方法返回值 方法名 参数列表
(1)基于applicationContext.xml的方式
(1)创建业务接口
public interface SomeService {
String doSome (String name,int age);
void show();
}
(2)创建业务实现
public class SomeServiceImpl implements SomeService{
@Override
public String doSome(String name, int age) {
System.out.println("doSome的业务功能实现....");
return "1";
}
@Override
public void show() {
System.out.println("show()的业务方法被执行.....");
}
}
(3)创建切面类,实现切面方法
@Aspect //交给AspectJ的框架去识别切面类
public class MyAspect {
/**
* 所有切面的功能都是由切面方法来实现的
* 可以将各种切面都在此类中进行开发
*
* 前置通知的切面方法的规范:
* 访问权限 public
* 方法的返回值:void
* 方法名称:自定义
* 方法没有参数
* 必须使用@Before注解来声明切入的时机和切入点
* 参数:@value 指定切入点表达式
* 业务方法:public String doSome(String name, int age)
*/
@Before(value = "execution(public * com.aspectj.s01.SomeServiceImpl.*(..))")
public void myBefore(){
System.out.println("切面方法中的前置通知功能实现");
}
}
(4)在applicationContext.xml文件中进行切面绑定
<!--创建业务对象-->
<bean id="someService" class="com.aspectj.s01.SomeServiceImpl"/>
<!--创建切面对象-->
<bean id="myAspect" class="com.aspectj.s01.MyAspect"/>
<!--绑定-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
(2)注解方式实现
(1)创建业务接口
public interface SomeService {
String doSome (String name,int age);
void show();
}
(2)创建业务实现
@Service("someServiceImpl")
public class SomeServiceImpl implements SomeService{
@Override
public String doSome(String name, int age) {
System.out.println("doSome的业务功能实现....");
return "1";
}
@Override
public void show() {
System.out.println("show()的业务方法被执行.....");
}
}
(3)创建切面类,实现切面方法
@Aspect //交给AspectJ的框架去识别切面类
@Component("myAspect")
public class MyAspect {
@Before(value = "execution(public * com.aspectj.s01.SomeServiceImpl.*(..))")
public void myBefore(){
System.out.println("切面方法中的前置通知功能实现");
}
}
(4)在applicationContext.xml文件中进行切面绑定
<!--基于注解的访问要添加包扫描-->
<context:component-scan base-package="com.aspectj.s01"/>
<!--绑定-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
5.后置通知@AfterReturning
(1)业务接口
public interface SomeService {
String doSome (String name,int age);
Student change();
}
(2)业务实现类
@Service("someServiceImpl")
public class SomeServiceImpl implements SomeService {
@Override
public String doSome(String name, int age) {
System.out.println("doSome的业务功能实现....");
return "abcd";
}
@Override
public Student change() {
System.out.println("change()方法被执行....");
return new Student("张三");
}
}
(3)切面类实现切面方法
@Aspect
@Component
public class MyAspect {
/**
* 后置通知方法的规范:
* 访问权限是public
* 方法没有返回值
* 方法名称自定义
* 方法有参数(如果目标方法没有返回值,则可以写无参的方法,一般写有参,切面方法的参数,就是目标方法的返回值)
* 使用@AfterReturning注解标明是后置通知
* 参数:
* value:指定切入点表达式
* returning:指定目标方法的返回值的名称,此名称必须和切面方法的参数名称一致
*/
@AfterReturning(value = "execution(* com.aspectj.s02.*.*(..))",returning = "obj")
public void myAfterReturning(Object obj){
System.out.println("后置通知功能的实现....");
if (obj != null) {
if (obj instanceof String){
obj=obj.toString().toUpperCase();
System.out.println("在切面方法中目标方法的返回值:"+obj);
}
if (obj instanceof Student){
Student student=(Student) obj;
student.setName("李四");
System.out.println("在切面方法中目标方法的返回值:"+student);
}
}
}
}
(4)在applicationContext.xml文件中进行绑定
<!--基于注解的访问要添加包扫描-->
<context:component-scan base-package="com.aspectj.s02"/>
<!--绑定-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
(5)测试
public class test2 {
@Test
public void test02(){
ApplicationContext ac=new ClassPathXmlApplicationContext("s02/applicationContext.xml");
//取出代理对象
SomeService someServiceImpl =(SomeService) ac.getBean("someServiceImpl");
String s = someServiceImpl.doSome("张三", 12);
System.out.println("测试类中目标方法的返回值:"+s);
}
@Test
public void test03(){
ApplicationContext ac=new ClassPathXmlApplicationContext("s02/applicationContext.xml");
//取出代理对象
SomeService someServiceImpl =(SomeService) ac.getBean("someServiceImpl");
Student student = someServiceImpl.change();
System.out.println("测试类中目标方法的返回值:"+student);
}
}
6.环绕通知@Around
通过拦截目标方法的方式,在目标方法的前后增强功能的通知。功能强大,一般事务使用此通知,可以任意改变目标方法的返回值
(1)业务接口
public interface SomeService {
String doSome (String name,int age);
}
(2)实现业务类
@Service("someServiceImpl")
public class SomeServiceImpl implements SomeService {
@Override
public String doSome(String name, int age) {
System.out.println("doSome的业务功能实现...."+name);
return "abcd";
}
}
(3)切面类实现切面方法
@Aspect
@Component
public class MyAspect {
/**
* 环绕通知方法的规范
* 访问权限:public
* 切面方法的返回值就是目标方法的返回值
* 方法名称自定义
* 方法有参数,此参数就是目标方法
* 回避异常
* 使用@Around注解声明是环绕通知
* 参数:
* value:指定切入点表达式
*/
@Around(value = "execution(* com.aspectj.s03.*.*(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
//前切功能实现
System.out.println("环绕通知中的前置功能实现....");
/**
* 目标方法调用
* proceed():获取目标方法
* getArgs():获取目标方法的参数
*/
Object obj=pjp.proceed(pjp.getArgs());
//后切功能实现
System.out.println("环绕通知中的后置功能实现....");
return obj.toString().toUpperCase();//改变了目标方法的返回值
}
}
(4)在applicationContext.xml文件中进行绑定
<!--基于注解的访问要添加包扫描-->
<context:component-scan base-package="com.aspectj.s03"/>
<!--绑定-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
(5)测试
public class test3 {
@Test
public void test03(){
ApplicationContext ac=new ClassPathXmlApplicationContext("s03/applicationContext.xml");
//取出代理对象
SomeService someServiceImpl =(SomeService) ac.getBean("someServiceImpl");
String s = someServiceImpl.doSome("张三", 12);
System.out.println("测试类中目标方法的返回值:"+s);
}
}
7.最终通知@After
无论目标方法是否正常执行,最终通知的代码都会被执行
切面类实现切面方法
@Aspect
@Component
public class MyAspect {
/**
* 最终通知方法的规范
* 访问权限:public
* 方法没有返回值
* 方法名称自定义
* 方法没有参数,如果有也只能是JoinPoint
* 使用@After注解声明是最终通知
* 参数:
* value:指定切入点表达式
*/
@After(value = "execution(* com.aspectj.s04.*.*(..))")
public void myAfter(){
System.out.println("最终通知的功能....");
}
}
8.给切入点表达式起别名@Pointcut
如果多个切面切入到同一个切入点,可以使用别名简化开发
使用@Pointcut注解,创建一个空方法,此方法的名称就是别名