本文主要是介绍week15_day04_SpringAOP,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
对昨天内容的总结:
注意:
类名首字母大写:Test1
包名小写:com.zgs
IOC
scope
作用域
singleton:单例 → 每一次取出都是同一个组件
prototype:原型 → 每一次取出都是全新的
scope也带来生命周期的变化
singleton:容器初始化的时候,开始生命周期
prototype:当你去获得这个组件的时候,才开始生命周期。生命周期没有destroy
collectionBean(xml配置文件)
最重要整合其他框架的时候,注册一些组件,组件又包含了这些collection类型的成员变量
都是在property标签下写子标签
array、list、set:array、list、set → 值类型:value标签 javabean:bean、ref标签
map:map → entry标签 →
map的key:值类型:key属性 ; javabean类型:key-ref属性
map的value:值类型:value属性或value子标签;javabean类型:value-ref属性或bean、ref子标签
properties:props → key和value都是值类型 → prop子标签 → key:prop标签的name属性;value:写在prop标签中间
注解(最重要)
<context:component-scan base-package=”com.cskaoyan”/>
2.3.1组件注册
@Component
@Service
@Repository
@Controller
写在类上
组件的id:默认是类名首字母小写;如果指定id,则通过在这些注解的value属性中指定id
@Component(“userComponent”)
@Service(“userService”)
类要在扫描包范围内
注入
值:@Value(“”) → value属性 → 给这个成员变量赋值
引用properties配置文件中的值:
1、引进properties配置文件
<context:property-placeholder location=”classpath:xxxproperties”/>
2、在@Value注解中引用key @Value(“${key}”)
javabean:从容器中取出组件
1、@Autowired → 按照类型取出 → 这个类型的组件在容器中只有一个
2、@Autowired+@Qualifier(“指定组件id”)
3、@Resource → 该类型组件在容器中只有一个
4、@Resource(name=“指定组件id”)
scope
@Scope注解写在类上 value属性为singleton或prototype;
如果为singleton,可以省略不写,默认
生命周期
@PostConstruct:init-method
@PreDestroy:destroy-method
测试
单元测试类维护为一个spring环境
成员变量可以使用注入相关的注解,从容器中取出组件给到他
方便去做单元测试
- AOP
作用:给容器中的组件做增强
之前做增强是通过oop(面向对象)编程,继承父类(静态代理)或传入委托类对象(动态代理)。
AOP:面向切面编程
之前是使用动态代理给一个类生成代理对象
aop是给容器中的组件批量生成代理对象 → 把要增强的方法放到一起 → 这个范围称为切面。aop是一个范围更广的增强
例子:张微在学校犯错了,老师叫张微家长,让张微变得更强
aop做的是开家长会,把家长都叫过来,让所有学生变得更强
利用aop可以做什么:动态代理可以做什么,aop就可以做什么
动态代理的InvocationHandler中 method.invoke(object,args); → 执行前后都可以做一些事情 → 汉堡模型
AOP
在软件业,面向切面编程,是指通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP(面向对象编程)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容。
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
method.invoke(object,args)执行的是增强前的对象的方法(或者说执行的是委托类对象的方法)。
可以使耦合度降低。
通用的事情可以放到增强的代码中,method.invoke(object,args);就不会和上下的校验代码、关闭流或开启事务、提交并关闭事务的代码耦合起来。
AOP的特点
AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码
Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类织入增强代码
经典应用:
事务管理、性能监视、安全检查、缓存 、日志等
这些系统服务通常被称为横切关注点
想象一下如果每个组件都单独去实现这些系统功能:
改变这些关注点的逻辑,修改各个模块当中的实现,方法的调用就会重复出现在各个模块中
组件会因为那些与自身核心业务无关的代码而变得混乱
未建立切面:
建立切面:
AOP编程术语
重点:pointcut、advice、aspect、target(委托类、被代理类、目标类)
建立切面:
aop底层将采用代理机制进行实现。
动态代理的两种机制
A
接口 + 实现类 :spring采用 jdk 的动态代理Proxy。
怎么做的?基于接口去实现。回顾。
缺点:如果目标类没有实现任何接口呢?jkd动态代理就无能为力。
B
实现类:spring 采用 cglib字节码增强。
怎么做的?基于继承。也可以实现动态代理。
cglib可以对任意类生成代理对象,它的原理是对目标对象进行继承代理,如果目标对象被final修饰,那么该类无法被cglib代理。
SpringAOP的本质就是动态代理,那么Spring到底使用的是JDK代理,还是cglib代理呢?
答:混合使用。如果被代理对象实现了接口,就优先使用JDK代理,如果没有实现接口,就用用cglib代理。
AOP实战
- 动态代理(手动方式)
jdk动态代理
cglib动态代理
spring也提供了cglib动态代理的包
- SpringAOP(半自动)
容器中注册组件 → 通过aop形式生成一个增强组件
通知
a. 导入依赖
b. 注册一个委托类target组件
c. 通知(按照什么样的方式来增强)
d. Proxy增强组件
package com.cskaoyan;import com.cskaoyan.HelloService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:application.xml")
public class MyTest {//@Autowired//HelloService helloService;//这取出的是目标类的组件@Autowired@Qualifier("helloServiceProxy")HelloService helloService;//这取出的是增强后的代理组件@Testpublic void mytest1() {helloService.sayHello("songge");}
}
思考:这样子来增强好不好?不好,每一个组件都对应一个ProxyFactoryBean,配置起来繁琐
- AspectJ(全自动)
aspect for java
批量增强:批量不是组件 → 而是组件中的方法 → 要被增强的方法所在类要注册在容器中
Pointcut:切入点。来指定方法 → 粒度更精细。
动态代理是将target委托类的所有方法做增强,如果某些方法不想增强,就得在增强的时候做if判断。
a. 引入全新依赖
建议使用groupid带org的这个依赖
讲一下切入点表达式
切入点表达式
批量的划分增强方法
execution(修饰符 返回值 包名.类名.方法名(形参))
讨论角度:
能否省略?能否通配?有没有特殊用法?
修饰符
private、public这些修饰符
可以省略不写:代表任意修饰符
返回值
能否省略:不能省略
能否通配:可以使用*来通配
特殊用法:如果返回值是javabean则需要写全类名;如果是基本类型或java.lang包目录下的可以直接写类名
包名+类名+方法名
能否省略:可以部分省略。→ 除了头和尾不能省略,中间的部分都可以省略,通过…来省略中间的部分
能否通配:可以通配*。头、中间、尾都可以使用*来通配。*可以代表一个单词或一个单词的一部分
形参
能否省略:可以省略不写 → 无参方法
能否通配:*来通配,通配的是单个返回值
特殊用法:返回值类似 如果参数是javabean,则要写全类名;如果是java.lang包目录下可以直接
<?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:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"><context:component-scan base-package="com.cskaoyan"/><aop:config><!--pointcut id为了方便别人引用他--><!--expression切入点表达式 → 匹配方法 → 指定增强方法--><!--指定了Hellservice中的sayHello方法--><aop:pointcut id="mypointcut" expression="execution(public void com.cskaoyan.service.HelloService.sayHello(String))"/><!--******修饰符******--><!--不写修饰符 代表任意的修饰符都可以--><aop:pointcut id="mypointcut2" expression="execution(void com.cskaoyan.service.HelloService.sayHello(String))"/><!--******返回值******--><!--*通配任意返回值--><aop:pointcut id="mypointcut3" expression="execution(* com.cskaoyan.service.HelloService.sayHello(String))"/><!--如果返回值是javabean则需要写全类名;如果是基本类型或java.lang包目录下的可以直接写--><aop:pointcut id="mypointcut4" expression="execution(String com.cskaoyan.service.HelloService.returnString())"/><aop:pointcut id="mypointcut5" expression="execution(com.cskaoyan.bean.User com.cskaoyan.service.HelloService.returnUser())"/><!-- ******包名类名方法名******--><!--除了头和尾不能省略,中间的部分都可以省略 → 中间使用..来进行省略--><aop:pointcut id="mypointcut6" expression="execution(com.cskaoyan.bean.User com..returnUser())"/><!--中间的任意一部分都可以省略--><aop:pointcut id="mypointcut7" expression="execution(com.cskaoyan.bean.User com.cskaoyan.service..returnUser())"/><!--任意一个地方都可以使用*来通配--><aop:pointcut id="mypointcut8" expression="execution(* *.cskao*.service..return*())"/><!--******形参******--><!--能否省略:省略代表无参方法 参考mypoincut8--><!--能否通配:*来通配 通配的单个返回值--><aop:pointcut id="mypointcut9" expression="execution(* *.cskao*.service..login(*,String,Integer))"/><!--使用..来通配任意数量的任意类型的参数--><aop:pointcut id="mypointcut10" expression="execution(* *.cskao*.service..login(..))"/><!--特殊点:返回值类似 如果参数是javabean,则要写全类名;如果是java.lang包目录下可以直接--><aop:pointcut id="mypointcut11" expression="execution(* *.cskao*.service..login(com.cskaoyan.bean.User))"/><!--pointcut属性写的是切入点表达式--><!--<aop:advisor advice-ref="" pointcut=""/>--><aop:advisor advice-ref="customAdvice" pointcut-ref="mypointcut11"/></aop:config></beans>
接下来的步骤分两种:
advisor(自己定义通知)
引用pointcut和通知
a. 通知组件
使用springaop是一样的,同样要实现MethodInterceptor接口,并且注册在容器中
b. aspectj中的advisor的配置
<aop → 引入aop的约束
1、复制已有的
2、官网appendix
3、创建模板
4、自己改造
aspect(提供通知)
定义pointcut和通知
时间:相对于委托类方法 → 基线(即下图中的method.invoke)
切面类(通知写在切面类中)
在通知的方法中定义做的事情 → 把该方法配置为对应的通知
也就是说,可以写一个方法,然后告诉它你属于什么样的通知。
在application.xml文件中把CustomAspect类配置为一个切面类
jointpoint
执行结果:
当委托类执行的方法没有异常的时候:
CustomAspect :
package com.cskaoyan.aop;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.springframework.stereotype.Component;import java.util.Arrays;@Component
public class CustomAspect {public void mybefore(JoinPoint joinPoint){System.out.println("正道的光");}//after类似于try-catch中的finally,也就是说,正道的光一定能照在大地上。//意思是,没执行委托类方法的时候就可以输出一个"正道的光"//同时一定可以执行到after,即一定可以"照在大地上"public void after(){System.out.println("照在了大地上");}//ProceedingJoinPoint接口继承了JoinPoint接口//around通知的作用:前面开启事务,后面提交并关闭事务。public Object around(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("around的before");//类似于method.invoke或methodInvocation.proceed,即委托类要执行的方法Object proceed = joinPoint.proceed();System.out.println("around的after");return proceed;}//可以拿到方法执行的返回值 → 需要在形参中定义 → Objectpublic void afterReturning(Object returnValue){System.out.println("返回值为:" + returnValue);}
}
HelloService:
package com.cskaoyan.service;import org.springframework.stereotype.Service;@Service
public class HelloService {public String sayHello(String name){String x = "hello " + name;System.out.println(x);return x;}
}
application.xml:
测试类:
package com.cskaoyan;import com.cskaoyan.service.HelloService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:application.xml")
public class MyAspectTest {@AutowiredHelloService helloService;@Testpublic void mytest2(){helloService.sayHello("songge");}
执行结果:
分析执行顺序:before→ around的before→ 委托类方法→ after-returning→ around的after→ after
当委托类执行的方法有异常的时候:
CustomAspect :
package com.cskaoyan.aop;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.springframework.stereotype.Component;import java.util.Arrays;@Component
public class CustomAspect {public void mybefore(JoinPoint joinPoint){//Signature signature = joinPoint.getSignature();//System.out.println(signature.getName());//System.out.println(signature.getDeclaringTypeName());//System.out.println(Arrays.asList(joinPoint.getArgs()));//System.out.println(joinPoint.getTarget().getClass());//委托类对象//System.out.println(joinPoint.getThis().getClass());//代理类对象System.out.println("正道的光");}//after类似于try-catch中的finally,也就是说,正道的光一定能照在大地上。//意思是,没执行委托类方法的时候就可以输出一个"正道的光"//同时一定可以执行到after,即一定可以"照在大地上"public void after(){System.out.println("照在了大地上");}//ProceedingJoinPoint接口继承了JoinPoint接口//around通知的作用:前面开启事务,后面提交并关闭事务。public Object around(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("around的before");//类似于method.invoke或methodInvocation.proceed,即委托类要执行的方法Object proceed = joinPoint.proceed();System.out.println("around的after");return proceed;}//可以拿到方法执行的返回值 → 需要在形参中定义 → Objectpublic void afterReturning(Object returnValue){System.out.println("返回值为:" + returnValue);}//用Exception或者Throwable接收public void afterThrowing(Exception exception){System.out.println("抛出的异常:" + exception.getMessage());}
}
HelloService:
package com.cskaoyan.service;import org.springframework.stereotype.Service;@Service
public class HelloService {public String createException(String name){String x = "create excetion";System.out.println(x);int i = 1/0;return x;}
}
application.xml:
测试类:
package com.cskaoyan;import com.cskaoyan.service.HelloService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:application.xml")
public class MyAspectTest {@AutowiredHelloService helloService;@Testpublic void mytest3(){helloService.createException("songge");}
}
执行结果:
分析执行结果:before→ around的before→ 委托类方法的前半部分→ after-throwing方法→ after方法。
委托类方法发生异常和不发生异常这两种情况下,before和after都能执行到。
所以说,正道的光一定能照在大地上。
使用注解来使用aspect
- 开启开关
- 切面类中使用注解
- 切入点表达式
切面类:
- 测试类
package com.cskaoyan;import com.cskaoyan.service.HelloService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;/*** @author shihao* @create 2020-07-17 8:07*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:application.xml")
public class MyTest {@AutowiredHelloService helloService;@Testpublic void test1() {helloService.sayHello("songge");}@Testpublic void test2() {helloService.createException("songge");}
}
测试结果:
test1:
:
test2:
可以发现,和上一个测试类的结果不太一样。因为这幅图:
不关注通知之间的顺序
关注的是通知和委托类方法的相对顺序
3.3.4.3使用自定义注解来指定增强方法
指哪儿打哪儿 pointcut
@CountTime注解 → 加在方法 → 计算该方法的执行时间
package com.cskaoyan.anno;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//@Target 该注解用来声明和限定注解使用的地方
@Target(ElementType.METHOD) //注解能够写在方法上
//@Rentention 该注解用来声明注解的保留级别
@Retention(RetentionPolicy.RUNTIME) //注解在运行时生效
public @interface CountTime {
}
切面类:
HelloService:(sayHello方法一个加了@CountTime注解,一个没加)
package com.cskaoyan.service;import com.cskaoyan.anno.CountTime;
import org.springframework.stereotype.Service;@Service
public class HelloService {@CountTimepublic void sayHello(int second) throws InterruptedException {System.out.println("hello songge");Thread.sleep(second * 1000);}public void sayHello(){System.out.println("hello songge");}
}
测试类:
package com.cskaoyan;import com.cskaoyan.service.HelloService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import java.lang.annotation.Target;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:application.xml")
public class CustomAnnotationTest {@AutowiredHelloService helloService;@Testpublic void mytest1() throws InterruptedException {helloService.sayHello(3);}@Testpublic void mytest2() throws InterruptedException {helloService.sayHello();}
}
执行结果:
test1:
test2:
作业:使用aspectj重构转账项目
事务的例子,通过spring的aspectj编程实现对service的转账操作增加事务处理。并测试是否生效。
使用提供的原先的TransactionUtil的形式进行startTransaction、rollback、commit
答:
DruidUtil作用:加载druid.properties、获得datasource从而拿到connection。
TransactionUtil:startTransaction、commit、rollback
TransferService中调用TransferDao,TransferService依赖于TransferDao
保证一件事情:在同一个service的方法中多次调用dao方法使用的是同一个connection。
答案见Homework04_teacher
这篇关于week15_day04_SpringAOP的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!