spring框架的学习(四)——Spring的AOP的概述及AOP的操作(基于aspectj的xml方式) 电玩女神 2022-06-01 08:58 137阅读 0赞 # 本文是转载文章,文章的来源:csdn博客 博主:李阿昀 转载文章: Spring的bean管理(注解方式) 转载原文地址:http://blog.csdn.net/yerenyuan\_pku/article/details/69663779 转载说明:转载"Spring的bean管理(注解方式)"的后面部分的内容 # # AOP的概述 # ## 什么是AOP ## Spring是用来解决实际开发中的一些问题的,AOP解决了OOP中遇到的一些问题,是OOP的延续和扩展。我们可从以下三个方面来理解AOP: 1. 扩展功能不是通过修改源代码而实现的。 可通过Struts2框架中的拦截器来理解。 2. AOP采用横向抽取机制实现。 要理解横向抽取机制,就必须认识纵向抽取机制。例如有如下的一个类: public class User { public void add() { 添加的逻辑... } } 现在我们在add()方法中要扩展一个功能,即日志添加功能,添加完该功能之后,可记录在什么时候添加了哪个用户。想到的最原始的方法就是直接修改源代码。 public class User { public void add() { 添加的逻辑... 直接写添加日志记录的代码以实现... } } 很显然这是一种愚蠢的做法,并且这儿还有一个原则——修改功能一般不是直接修改源代码来实现的。顺其自然地就要来讲纵向抽取机制了,这时我们可编写一个BaseUser类。 public class BaseUser { public void wirtelog() { 记录日志的逻辑... } } 接下来让User类继承BaseUser类,如下: public class User extends BaseUser { public void add() { 添加的逻辑... // 记录日志 super.wirtelog(); } } 这样是不是就万事大吉了呢?你懂的!因为当父类的方法名称变化时,子类调用的方法也必然要进行修改。最后终于要讲横向抽取机制了,横向抽取机制分为两种情况,下面分别加以简单阐述。 有接口情况的横向抽取机制: 例如有一个如下接口: public interface UserDao { public void add(); } 接口的一个实现类如下: public class UserDaoImpl implements UserDao { public void add() { ... } } 我们现在即可使用动态代理技术增强类里面的方法,即创建接口的实现类代理对象,并增强UserDaoImpl类里面的add()方法。 无接口情况的横向抽取机制: 例如有一个如下User类: public class User { public void add() { ... } } 我们也可使用动态代理技术增强类里面的方法,即创建被增强方法所在类的子类代理对象,并增强User类里面的add()方法。 3. AOP底层使用动态代理实现 * 有接口情况:创建接口实现类代理对象 * 没有接口情况:创建增强方法所在类的子类代理对象 ## 为什么学习AOP ## 在不修改源代码的情况下,即可对程序进行增强。AOP可以进行权限校验、日志记录、性能监控和事务控制。 ## Spring的AOP的由来 ## AOP最早是由AOP联盟的组织提出的,他们制定了一套规范。Spring将AOP思想引入到框架中,且必须遵守AOP联盟的规范。 ## Spring的AOP的底层实现 ## Spring的AOP的底层用到了两种代理机制: * JDK的动态代理:针对实现了接口的类产生代理。 * Cglib的动态代理:针对没有实现接口的类产生代理,应用的是底层的字节码增强的技术,生成当前类的子类对象。 ## AOP开发中的相关术语 ## 以下是比较专业的术语。 * Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点。 * Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。 * Advice(通知/增强):所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。通知分为前置通知、后置通知、异常通知、最终通知和环绕通知(切面要完成的功能)。 * Aspect(切面):是切入点和通知的结合。 * Target(目标对象):代理的目标对象(要增强的类) * Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。 * Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类。 * Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法或Field。 例如有如下一个类: public class User { public void add() { } public void update() { } public void delete() { } } 下面是用比较通俗易懂的话来阐述AOP开发中的常见的相关术语: * 连接点:在User类里面有3个方法,这3个方法都可以被增强,类里面的哪些方法可以被增强,这些方法就可被成为连接点。 * 切入点:在一个类中可以有很多的方法被增强,在实际操作中,如若只增强了类里面的add方法,则实际增强的方法被称为切入点。 * 增强/通知:比如增强User类里面的add方法,在add方法中添加了日志功能,这个日志功能就称为增强。 通知类型: * 前置通知:在增强的方法执行之前进行操作。 * 后置通知:在增强的方法执行之后进行操作。 * 环绕通知:在增强的方法执行之前和执行之后进行操作。 * 最终通知:增强了两个方法,执行第一个方法,执行第二个方法,在第二个方法执行之后进行操作。 也可理解为后置通知后面执行的通知或者无论目标方法是否出现异常,最终通知都会执行。 * 异常通知:程序出现异常之后执行的通知。 * 切面:把增强应用到切入点的过程。即把具体增强的逻辑用到具体的方法上面的过程。 * 目标对象:增强的方法所在的类,即要增强的类。 * Weaving(织入):是指把增强应用到目标对象的过程。即把把advice应用到target的过程。 # Spring的基于AspectJ的AOP开发 # ## @AspectJ的简介 ## AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法所以它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。AspectJ是一个基于Java语言的AOP框架。Spring2.0以后新增了对AspectJ切点表达式的支持。@AspectJ是AspectJ1.5新增的功能,通过JDK5注解技术,允许直接在Bean类中定义切面。新版本Spring框架,建议使用AspectJ方式来开发AOP,使用AspectJ需要导入Spring AOP和AspectJ相关的Jar包。 从上面的阐述中,我们应认识到AspectJ并不是Spring框架的一部分,而是一个单独的面向切面的框架,只不过它经常和Spring框架一起使用进行AOP的操作而已。 使用AspectJ方式来开发AOP共有两种方式: 1. 基于AspectJ的xml配置文件的方式 2. 基于AspectJ的注解的方式 只不过本文讲解的是基于AspectJ的xml配置文件的方式,下文再讲第二种方式。 ## Spring使用AspectJ进行AOP的开发:XML的方式 ## 第一步,引入相应的Jar包 上面我说过,除了导入最基本的Jar包外,使用AspectJ还需要导入Spring AOP和AspectJ相关的Jar包。 * Spring的传统AOP的开发的包: * spring-aop-4.2.4.RELEASE.jar * aopalliance-1.0.jar * AspectJ的开发包 * aspectjweaver-1.8.7.jar * spring-aspects-4.2.4.RELEASE.jar 第二步,编写目标类 在src目录下创建一个cn.itcast.aop包,并在该包下编写一个目标类。 public class Book { public void add() { System.out.println("book add................."); } } 第三步,创建增强的类以及增强的方法 public class MyBook { // 前置通知 public void before1() { System.out.println("before........"); } } 我们现在要求在Book类里面的add方法之前执行MyBook类里面的before1的方法。 第四步,在Spring配置文件中进行配置 1. 在Spring配置文件中引入aop约束 <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"> 2. 配置两个类对象 <!-- book对象 --> <bean id="book" class="cn.itcast.aop.Book"></bean> <bean id="myBook" class="cn.itcast.aop.MyBook"></bean> 3. 配置AOP操作,即需要配置切入点和切面。 <!-- 配置AOP的操作 --> <aop:config> <!-- 配置切入点,对Book类里面的所有方法都增强 --> <aop:pointcut expression="execution(* cn.itcast.aop.Book.*(..))" id="pointcut1"></aop:pointcut> <!-- 配置切面 aop:aspect标签里面使用属性ref,ref属性值写增强类的bean的id值 --> <aop:aspect ref="myBook"> <!-- 增强类型 method属性:增强类的方法名称 pointcut-ref属性:切入点的id值 --> <!-- 前置通知 --> <aop:before method="before1" pointcut-ref="pointcut1"></aop:before> </aop:aspect> </aop:config> 第五步,编写一个单元测试类并进行测试 在cn.itcast.aop包下编写一个TestDemo单元测试类。 public class TestDemo { @Test public void testUser() { ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml"); Book book = (Book) context.getBean("book"); book.add(); } } 其实我们也可以整合Junit单元测试,Spring对Junit4进行了支持,可以通过注解方便的测试Spring程序,所以就不必写那么麻烦的单元测试类了。首先导入如下Jar包: ![这里写图片描述][SouthEast] 然后编写如下的单元测试类: @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("/bean2.xml") // 或者也可写为:@ContextConfiguration("classpath:bean2.xml") public class TestDemo { @Autowired private Book book; @Test public void demo1() { book.add(); } } ### 演示其他通知类型 ### 先将MyBook增强类的代码修改为: public class MyBook { // 前置通知 public void before1() { System.out.println("before........"); } // 后置通知 public void after11() { System.out.println("after..........."); } // 环绕通知 public void around1(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("方法之前执行..........."); // 让被增强的方法执行 joinPoint.proceed(); System.out.println("方法之后执行..........."); } } 然后再在Spring配置文件中进行配置。 <!-- 配置AOP的操作 --> <aop:config> <!-- 配置切入点,对Book类里面的所有方法都增强 --> <aop:pointcut expression="execution(* cn.itcast.aop.Book.*(..))" id="pointcut1"></aop:pointcut> <!-- 配置切面 aop:aspect标签里面使用属性ref,ref属性值写增强类的bean的id值 --> <aop:aspect ref="myBook"> <!-- 增强类型 method属性:增强类的方法名称 pointcut-ref属性:切入点的id值 --> <!-- 前置通知 --> <aop:before method="before1" pointcut-ref="pointcut1"></aop:before> <!-- 后置通知 --> <aop:after-returning method="after11" pointcut-ref="pointcut1"></aop:after-returning> <!-- 环绕通知 --> <aop:around method="around1" pointcut-ref="pointcut1"></aop:around> </aop:aspect> </aop:config> ## 切入点表达式 ## 通过execution函数,可以定义切点的方法切入。 语法为:`execution(<访问修饰符>?<返回类型><方法名>(<参数>)<异常>)`。 例如: * 匹配所有类的public方法:`execution(public *.*(..))` * 匹配指定包下所有类的方法:`execution(* cn.itcast.dao.*(..))`,但不包含子包 * `execution(* cn.itcast.dao..*(..))`,`..*`表示包、子孙包下所有类。 * 匹配指定类所有方法:`execution(* cn.itcast.service.UserService.*(..))` * 匹配实现特定接口的所有类的方法:`execution(* cn.itcast.dao.GenericDAO+.*(..))` * 匹配所有save开头的方法:`execution(* save*(..))` * 匹配所有类里面的所有的方法:`execution(* *.*(..))` # Log4j操作 # 在项目开发中,我们通常会导入类似这样的日志Jar包: ![这里写图片描述][SouthEast 1] 关于这些日志Jar包之间的关系我也搞的不是很清楚,只能作简单的记录。 使用Log4j,可以查看到当前运行程序中对象创建的过程,也可以看到更详细的信息。Log4j适合使用在程序调试中。 例如,在本文中讲解上面的Web项目使用Log4j。 首先导入log4j的jar包,如下: ![这里写图片描述][SouthEast 2] 然后添加log4j配置文件——log4j.properties,添加到src目录下面,该文件内容如下: ### direct log messages to stdout ### log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target=System.err log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{ 1}:%L - %m%n ### direct messages to file mylog.log ### log4j.appender.file=org.apache.log4j.FileAppender log4j.appender.file.File=d\:mylog.log log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{ 1}:%L - %m%n ### set log levels - for more verbose logging change 'info' to 'debug' ### log4j.rootLogger=info, stdout, file 粗略讲解如下图: ![这里写图片描述][SouthEast 3] # IoC配置文件和注解混合使用 # 实际开发中,我们都会混合使用IoC配置文件和注解,一般是使用配置文件方式创建对象,使用注解方式注入属性。当然了,你亦可另辟蹊径。下面我举个例子来演示。 在src目录下创建一个cn.itcast.xmlanno包,并在该包下编写一个BookDao类、PersonDao类、BookService类。 * BookDao类 public class BookDao { public void update() { System.out.println("book dao............."); } } * PersonDao类 public class PersonDao { public void update() { System.out.println("person dao............."); } } * BookService类 public class BookService { public void update() { System.out.println("service............"); } } 接着创建对象,使用配置文件实现。 <bean id="bookService" class="cn.itcast.xmlanno.BookService"></bean> <bean id="bookDao" class="cn.itcast.xmlanno.BookDao"></bean> <bean id="personDao" class="cn.itcast.xmlanno.PersonDao"></bean> 也有人说创建对象,能用注解就用注解,而不要写上面这种乱七八糟的东西。我觉得也蛮有道理的。 然后在BookService类里面注入BookDao类以及PersonDao类的对象,使用注解方式。 public class BookService { @Resource(name="bookDao") private BookDao bookDao; @Resource(name="personDao") private PersonDao personDao; public void update() { System.out.println("service............"); bookDao.update(); personDao.update(); } } 注意,不要忘了开启注解的扫描: <context:component-scan base-package="cn.itcast"></context:component-scan> 最后再在cn.itcast.xmlanno包下编写一个TestDemo单元测试类。 public class TestDemo { @Test public void testBook() { ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml"); BookService bookService = (BookService) context.getBean("bookService"); bookService.update(); } } 测试即可。 # Spring整合Web项目 # 现在我来介绍如何使用Spring整合Web项目。在真正的开发中,都会在action中调用service层的代码,service层再调用dao层的代码。下面我就举个例子来演示。 首先创建Web项目,引入Struts2的开发包,可参考struts-2.3.24中的apps下的一些示例代码,其中struts2-blank.war是一个Struts2的空的工程。我们只需要将struts2-blank.war解压后进入到WEB-INF下的lib中查看,即可知道要导入哪些Struts2的开发包。 ![这里写图片描述][SouthEast 4] 然后在src目录下创建一个cn.itcast.action包,并在该包下编写一个Action类。 public class UserAction extends ActionSupport { @Override public String execute() throws Exception { System.out.println("action........."); return NONE; } } 接着就要完成该UserAction类的配置,在src目录下引入Strust2框架的核心配置文件——struts.xml,其内容如下: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN" "http://struts.apache.org/dtds/struts-2.3.dtd"> <struts> <package name="demo" extends="struts-default" namespace="/"> <action name="user" class="cn.itcast.action.UserAction"></action> </package> </struts> 不要忘了配置核心过滤器,打开web.xml,在web.xml文件中添加如下配置: <!-- 配置Struts2的过滤器 --> <filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> 接着再在src目录下创建一个cn.itcast.service包,并在该包下编写一个UserService类。 public class UserService { public void add() { System.out.println("service............"); } } 上面基本上将Struts2框架的开发环境搭建好了,接下来就要使用Spring整合该Web项目了。 首先导入Spring框架开发的Jar包: ![这里写图片描述][SouthEast 5] 既然上面已讲了log4j日志包的操作,这里还是在src目录下引入log4j.properties文件。 ### direct log messages to stdout ### log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target=System.err log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{ 1}:%L - %m%n ### direct messages to file mylog.log ### log4j.appender.file=org.apache.log4j.FileAppender log4j.appender.file.File=d\:mylog.log log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{ 1}:%L - %m%n ### set log levels - for more verbose logging change 'info' to 'debug' ### log4j.rootLogger=info, stdout, file 然后再在src目录下引入Spring的核心配置文件——bean1.xml,创建并管理UserService类的对象。 <?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" 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"> <bean id="userService" class="cn.itcast.service.UserService"></bean> </beans> 接着改写UserAction类的代码,让其调用service层的代码。 public class UserAction extends ActionSupport { @Override public String execute() throws Exception { System.out.println("action........."); ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml"); UserService userService = (UserService) context.getBean("userService"); userService.add(); return NONE; } } 最后我们在浏览器地址栏中输入地址`http://localhost:8080/Spring_day02_web/user.action`进行访问,并多次刷新,在Eclipse控制台中可以看到如下信息: ![这里写图片描述][SouthEast 6] 这就产生了一个问题:由于action对象是一个多实例的对象,所以每次访问action都会创建一个action对象。虽然功能是可以实现,但是性能很低下。为了解决这个问题,需要使用到2个技术: * ServletContext对象 * 监听器 在服务器启动的时候创建ServletContext对象,使用监听器可以知道ServletContext对象在什么时候创建,然后加载配置文件,创建配置的对象,把创建的对象放到ServletContext域里面。但在Spring里面不需要我们写这些操作的代码,因为它已经帮我们进行了封装,Spring里面封装了一个监听器,我们只需要配置监听器就可以了。使用Spring里面的监听器的时候,需要导入一个jar包,这个jar包用于整合Web项目。 ![这里写图片描述][SouthEast 7] 接着在web.xml文件中配置Spring的监听器,这样在服务器启动的时候,就会加载Spring的配置文件了。 <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> 这时我们启动服务器,会在Eclipse的控制台中看到如下异常信息: ![这里写图片描述][SouthEast 8] 这是因为服务器启动的时候,会默认到WEB-INF下面找寻名称是applicationContext.xml的配置文件。要解决这个异常,就要手动配置让服务器找寻哪个Spring配置文件,可在web.xml文件中添加如下配置: <!-- 指定找到Spring配置文件的位置和名称 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:bean1.xml</param-value> </context-param> 这样问题就解决了。但我们还要明白Spring整合Web项目还没完,也不用急,下文我会接着介绍的。 [SouthEast]: /images/20220601/bcda1d9e597744cca6112ea7c1eb292f.png [SouthEast 1]: /images/20220601/8ee8524b71aa4fa3b62d8a61d91526b4.png [SouthEast 2]: /images/20220601/2ae6d5f75c4c4c30b166cfff1bc7c089.png [SouthEast 3]: /images/20220601/0d7d31cf9f0b41af8fdede59cae810a1.png [SouthEast 4]: /images/20220601/1d4138674b4643feba19ed33584fef98.png [SouthEast 5]: /images/20220601/375a7563a2e5415eb1eec52c14332257.png [SouthEast 6]: /images/20220601/13b454836969480ebfcbf8bb1930e8b5.png [SouthEast 7]: /images/20220601/200000f48784446f9b7197c7fa770616.png [SouthEast 8]: /images/20220601/37833861fed343708fba45f9f2eed8ff.png
还没有评论,来说两句吧...