AOP
- 引入:Spring Aop(Aspect Oriented Programming)面向切面编程。是现在比较常用的技术,在1990年的时候就出现 ,当时面向对象已经趋于成熟,在开发中有些局限性,所以提出了aop面向切面编程。
- 概念:Aop就是在不增加代码的情况下,还添加了新的功能。日志,事务,等等。相当在的切面上进行调用一些新的方法(1.量体温,2.扫描码,3.登记信息等等),SpringAOP是继spring ioc的 spring 的一大特点。也是spring框架的核心内容。Aop是一种思想。只要符合aop思想的技术就都是aop的实现(生活当中保安相当是aop切面)Aop是建立在Java的动态代理机制之上,spring是最受欢迎的的框架。
- 底层实现原理:Spring ioc:使用的反射机制,
Spring aop:使用代理机制(动态代理)
代理模式(设计模式)
-
概念: 代理模式其实就是在写代码的时候,可以抽象出来一个代理,以后想去用一个类的时候,不能直接使用,可以找代理来进行处理。有一个代理类做被代理对象,和代理的对象之间一个桥梁。
- 第一种代理:静态代理----只可以做一个代理;
- 第二种代理:动态代理----可以在不同时间做不同的代理
-
代理模式一般涉及到的角色:
- 抽象角色:声明真实对象和代理对象的共同接口。
- 代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。可以完全作为你的代理去做事情;
- 真实角色:代理角色所代表的真实对象,是最终要引用的对象。
静态代理
- 代理类和委托类必须实现同一个接口
- 代理类中持有被委托类的对象
动态代理
动态代理的本质和静态代理是一样的,只不过(动态)代理对象是通过反射代理机制动态创建的;
- jdk自带的动态代理机制 (是需要有由一个接口)
- cglib动态代理机制(不需要接口)—>第三方的动态代理的工具包
//创建一个委托类的对象
Pan pan = new Pan();
//使用Jdk提供的动态代理
KindWoMan proxyInstance = (KindWoMan) Proxy.newProxyInstance(Main.class.getClassLoader(), Pan.class.getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("say")) {
String say = pan.say();
return "hello" + say;
}
return null;
}
});
//调用代理对象的say方法
String say = proxyInstance.say();
new XiMeng().rec(say);
-
CGLIB代理机制
- Spring AOP的实现是基于Java的代理机制,从JDK1.3开始就支持代理功能,但是性能成为一个很大问题,为了解决JDK代理性能问题,出现了CGLIB代理机制(继承你的代理类)。它可以生成字节码,所以它的性能会高于JDK代理。Spring支持这两种代理方式。但是,随着JVM(Java虚拟机)的性能的不断提高,这两种代理性能的差距会越来越小。
- Hibernate作为一个比较受欢迎的ORM框架,同样使用CGLIB来代理单端(多对一和一对一)关联(延迟提取集合使用的另一种机制)。很多的框架中的代理使用的cglib代理来实现,Java的原生的动态代理的性能比cglib要弱好多。
CGLIB底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。除了CGLIB库外,脚本语言(如Groovy何BeanShell)也使用ASM生成字节码。ASM使用类似SAX的解析器来实现高性能。
-
AOP中的代理
AOP适合于那些具有横切(切面)逻辑的应用:如性能监测(druid),访问控制(单点登录),事务管理(增删改 都要对你的数据库操作;—你给别人的转账;)、缓存(当你的业务走到这个切面后,写缓存;)、对象池管理以及日志记录(tomcat写日志)。AOP将这些分散在各个业务逻辑中的代码通过横向切割的方式抽取到一个独立的模块中(以前的我们在各个方法中去调用的方法比如写日志方法; 我们可以抽取出来放在切面上; 只要你的方法进入到我们的切面就会调用方法)。**AOP 实现的关键就在于 AOP 框架自动创建的 AOP 代理,AOP 代理则可分为静态代理和动态代理两大类,其中静态代理是指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强;而动态代理则在运行时借助于 JDK 动态代理、(运行时增强)CGLIB 等在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强。**Cglib在spring当中很多地方都使用他来做代理,主要使用的动态代理(运行时增强)
代理对象的方法 = 增强处理 + 被代理对象的方法
Spring AOP 则采用运行时(动态代理cglip)生成 AOP 代理类,因此无需使用特定编译器进行处理。由于 Spring AOP 需要在每次运行时生成 AOP 代理,因此性能略差一些。 -
常用名词
AOP使用场景(框架本身的用的很多)
AOP用来封装横切关注点(切面),具体可以在下面的场景中使用
Authentication 权限
Caching 缓存
Context passing 内容传递
Error handling 错误处理
Lazy loading 懒加载
Debugging 调试
logging, tracing, profiling and monitoring 记录跟踪 优化 校准
Performance optimization 性能优化
Persistence 持久化
Resource pooling 资源池
Synchronization 同步
Transactions 事务
Spring在以后我们的项目开发的过程中: 日志管理;缓存操作;事务管理; 性能管理;使用的频率比较大。
AOP底层实现
aop的底层实现----动态代理,java里边提供了代理,静态代理(一开始的时候就知道给哪个做代理,编译的时候就已经知道(编译时增强)), 动态代理(只有运行的时候才会知道做哪个代理----只有在运行的时候才会知道代理—运行时增强;);java里边也有代理,cglib这个代理库,是比原来的java里边的代理要性能优越一些,但是现在jdk更新了很多,使用的机器比较的好,它们之间差别就不是很大了。
AOP的使用
相关概念:
- 切面(Aspect):其实就是共有功能的实现。如日志切面、权限切面、事务切面等。在实际应用中通常是一个存放共有功能实现的普通Java类,之所以能被AOP容器识别成切面,是在配置中指定的。(这个就相当是安检门)以后操作就可以在这个门上进行安防,—当在切面上进行安装的软件,就是相当对切面进行编程。
- 通知(Advice):是切面的具体实现。以目标方法为参照点(刷卡),根据放置的地方不同,可分为前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)与环绕通知(Around)5种。在实际应用中通常是切面类中的一个方法,具体属于哪类通知,同样是在配置中指定的。
- 连接点(Joinpoint):就是程序在运行过程中能够插入切面的地点。例如,方法调用、异常抛出或字段修改等,但Spring只支持方法级的连接点。(针对方法进行连接)
- 切入点(Pointcut):用于定义通知应该切入到哪些连接点上。不同的通知通常需要切入到不同的连接点上,这种精准的匹配是由切入点的正则表达式来定义的。
- 目标对象(Target):就是那些即将切入切面的对象,也就是那些被通知的对象。这些对象中已经只剩下干干净净的核心业务逻辑代码了,所有的共有功能代码等待AOP容器的切入。
- 代理对象(Proxy):将通知应用到目标对象之后被动态创建的对象。可以简单地理解为,代理对象的功能等于目标对象的核心业务逻辑功能加上共有功能。代理对象对于使用者而言是透明的,是程序运行过程中的产物。(这个是由springaop提供代理对象)
- 织入(Weaving):将切面应用到目标对象从而创建一个新的代理对象的过程。这个过程可以发生在编译期、类装载期及运行期,当然不同的发生点有着不同的前提条件。譬如发生在编译期的话,就要求有一个支持这种AOP实现的特殊编译器;发生在类装载期,就要求有一个支持AOP实现的特殊类装载器;只有发生在运行期,则可直接通过Java语言的反射机制与动态代理机制来动态实现。
举例
通过spring来进行aop的模拟,就是模拟一个,切面,主流业务,然后通过代理对象把切面植入到主流业务中,当主流业务执行的时候,就会启动切面。
- 需要导入jar包----spring的jar,aop依赖于ioc,所以在使用的时候直接导入spring-context即可。
- 主流的业务
//主流业务,使用时调用主流业务的方法
public class Target {
//主流业务的方法
public void showInfo(String name) {
System.out.println("这是一个主流业务:" + name);
}
}
- 切面编程
在进行写切面时,需要实现一个接口(MethodInterceptor) 它是一个拦截器,可以拦截你的主流业务。
//切面
//MethodInterceptor是一个拦截接口
public class Ascpect implements MethodInterceptor {
//当主流业务走到此切面,会被拦截然后执行切面中的方法
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
before(); //调用切面上的方法
invocation.proceed(); //放行主流业务
return null;
}
//切面上的方法(前置方法)
public void before(){
System.out.println("现在是前置操作---已经进入到前置方法中");
}
}
- 测试方法
//测试方法,将主流业务和切面整合在一起
public class Test {
public static void main(String[] args) {
Target target = new Target(); //主流业务对象
ProxyFactory factory = new ProxyFactory(); //创建一个代理工厂
factory.addAdvice(new Ascpect()); //通过代理将切面加入
factory.setTarget(target); //通过代理将主流业务加入
Target ta = (Target)factory.getProxy(); //获取代理
ta.showInfo("lyx"); //通过代理调用主流业务的方法
}
}
拦截器和过滤器
1.拦截器的本质使用的java的反射实现; 过滤器使用的函数回调;
2.拦截器不依赖于servlet; 他是spring中的; 过滤器依赖于servlet容器;
3.拦截器是针对action请求进行拦截; 过滤器是对所有的请求进行过滤;
4.拦截器可以访问action中的上下文; 值栈;对象; 过滤器不行;
5.在action的声明周期中;拦截器可以多次使用; 过滤器只会在容器初始化的时候使用;
Spring 的aop通知(可以看成是拦截器,过滤器):
过滤器:filter----底层实现 使用的函数回调;
监听器:listener----观察者模式;
拦截器:Interceptor—他使用到你aop里边关于拦截;他的实现方式是 动态代理和反射机制;
AOP的实现方式
- xml文件配置的方式
- 是要写主流业务的接口
//主流业务的接口
public interface ServiceTarget {
void sayhello() throws Exception; //主流业务
}
- 主流业务接口的实现
public class ServiceTargetImpl2 implements ServiceTarget{
private String name;
public String getName(){
return name;
}
public void setName(String name){
this.name=name;
}
@Override
public void sayhello() throws Exception {
System.out.println("这是一个主流业务---完成集体的业务操作的方法" + name);
}
}
- 四种通知(前置,后置,环绕,异常)
public class BeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("这是一个进行日志打印的前置通知" + "现在的主流业务是:" + method.getName());
}
}
public class AfterAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("后置通知已经启动-----主流业务是" + method.getName());
}
}
public class AroundAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("环绕通知---");
invocation.proceed();
System.out.println("环绕通知---");
return null;
}
}
public class ThrowAdvice implements ThrowsAdvice {
public void afterThrowing(Method method, Object[] args,
Object target, Exception e) throws Throwable {
System.out.println("当前我们的代码发生异常情况:"+method.getName()+
"错误信息:"+target.toString()+"\t"+e.getMessage());
}
}
- 配置xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!--1.给主流业务注入相关内容-->
<!-- <bean id="serviceTargetImpl" class="com.lyx.sp.demo02.ServiceTargetImpl">
<property name="name" value="lyx"></property>
</bean>-->
<bean id="serviceTargetImpl2" class="com.lyx.sp.demo02.ServiceTargetImpl2">
<property name="name" value="lyx2"></property>
</bean>
<!--2.通知注入-->
<bean id="beforeAdvice" class="com.lyx.sp.demo02.BeforeAdvice"></bean>
<bean id="afterAdvice" class="com.lyx.sp.demo02.AfterAdvice"></bean>
<bean id="aroundAdvice" class="com.lyx.sp.demo02.AroundAdvice"></bean>
<bean id="throwAdvice" class="com.lyx.sp.demo02.ThrowAdvice"></bean>
<!--3.配置Spring代理对象-->
<bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--对此接口进行代理-->
<property name="proxyInterfaces"><list><value>com.lyx.sp.demo02.ServiceTarget</value></list></property>
<!--将通知植入到代理对象上-->
<property name="interceptorNames"><list><value>beforeAdvice</value><value>afterAdvice</value>
<value>aroundAdvice</value>
<value>throwAdvice</value></list></property>
<!--主流业务对象-->
<property name="target" ref="serviceTargetImpl2"></property>
</bean>
</beans>
- 测试
public class Test {
public static void main(String[] args) throws Exception{
ApplicationContext app = new ClassPathXmlApplicationContext("bean.xml");
ServiceTarget serviceTarget = app.getBean("proxyFactoryBean",ServiceTarget.class);
serviceTarget.sayhello();
}
}
- @aspectj注解驱动的切面,就是使用注解方式来实现
Aspectj这个进行切面aop—是一个专门做出来进行注解方式开发的。
AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。
- 添加aspectj的注解
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjrt -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.1</version>
</dependency>
- 主流业务类
@Component
public class Monkey {
public void stealPeaches(String name) {
System.out.println("这是一只聪明的猴子,名字叫:" + name);
}
}
- 切面
@Component
@Aspect //此注解表示就是这一个切面
public class Guardian {
@Pointcut("execution(* com.lyx.sp.demo03.Monkey.stealPeaches(..))")
public void foundMonkey(){}
@Before(value = "foundMonkey()")
public void foundBefore(){
System.out.println("猴子进入果园");
}
@AfterReturning(value = "foundMonkey()&& args(name,..)")
public void foundAfter(String name){
System.out.println("猴子被抓住。名字是:" + name);
}
}
- 配置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"
xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="
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
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
<!-- <bean id="monkey" class="com.sp.aop.Monkey"></bean> -->
<!-- <bean id="guardian" class="com.sp.aop.Guardian"></bean> -->
<!--自动扫描包 -->
<context:component-scan base-package="com.lyx.sp.demo03"></context:component-scan>
<!-- 支持aspectj -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
- 测试
public class Test {
public static void main(String[] args) {
ApplicationContext app = new ClassPathXmlApplicationContext("bean01.xml");
Monkey monkey = app.getBean("monkey",Monkey.class);
monkey.stealPeaches("孙悟空");
}
}
日志文件的例子
- 主流业务类
@Component
public class Log {
public void adduser(){
System.out.println("增");
}
public void deleteuser(){
System.out.println("删");
}
public void updateuser(){
System.out.println("改");
}
public void selectuser(){
System.out.println("查");
}
}
- 切面
@Component
@Aspect
public class LogAdvice {
// 前置通知
// @Before("")//代表目标对象中每一个方法执行都会启动前置通知;
@Before("execution(void *user(..))")//加入切面约束
public void before(JoinPoint jp) {
String name=jp.getSignature().getName();//获取你的目标对象的名字
System.out.println("前置通知已经启动:-------"+"目标对象业务的名称为:"+name);
}
// 后置通知
@After("execution(void *user(..))")
public void after(JoinPoint jp) {
String name=jp.getSignature().getName();//获取你的目标对象的名字
System.out.println("后置通知已经启动:-------"+"目标对象业务的名称为:"+name);
}
// 环绕通知
@Around("execution(void *user(..))")
public void around(ProceedingJoinPoint pj) throws Throwable {
String name=pj.getSignature().getName();//获取你的目标对象的名字
System.out.println(name+"-----环绕通知之前执行------------------");
pj.proceed();//继续往后走执行业务
System.out.println(name+"-----环绕通知之后执行------------%%%%%%------");
}
// 最终通知;
@AfterReturning("execution(void *user(..))")
public void final01(JoinPoint jp) {
String name=jp.getSignature().getName();//获取你的目标对象的名字
System.out.println(name+"执行之后的操作-----最终通知!!!");
}
// 这个就是异常通知
@AfterThrowing("execution(void *user(..))")
public void execption(JoinPoint jp) {
String name=jp.getSignature().getName();//获取你的目标对象的名字
System.out.println("执行方法的时候出现了异常"+name+"这个里边可能存在异常!!!");
}
}
- 配置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"
xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="
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
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
<!--自动扫描包 -->
<context:component-scan base-package="com.lyx.sp.demo04"></context:component-scan>
<!-- 支持aspectj -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
- 测试
public class Test {
public static void main(String[] args) {
ApplicationContext app = new ClassPathXmlApplicationContext("bean01.xml");
Log log = app.getBean("log",Log.class);
log.adduser();
}
}
事务管理的例子
xml文件方式比较麻烦
使用注解的方式:
xml文件
然后在要进行事务的方法上加上
- 纯pojo的方式
- 注入式的aspectj切面的方式