AOP 全程Aspect Oriented Programming,直译就是面向切面编程。和POP、OOP类似,它也是一种编程思想。OOP强调的是封装、继承、多态,也就是功能的模块化。而AOP则是OOP的补充,它强调的是切面,在运行时动态地将代码切入到类的指定方法、指定位置上的编程思想,也就是将业务代码和业务先后的代码分离出来(解耦),将日志、权限验证等功能抽取出来而后重用。
在Spring中,采用动态代理的方式来表达AOP。(并不是全部的AOP都是使用动态代理来,好比AspectJ采用编译时建立代理对象,比运行时建立效率更高)
动态代理通常有两种实现方式,一种是JDK原生动态代理,要求被代理对象必须实现接口,而且只能代理接口中的方法(本质是建立一个实现接口的代理对象)。另外一种是CGlib,能够代理全部的方法(本质是建立一个代理对象,继承被代理对象)。Spring中使用的是CGlib的方式。java
废话很少说,直接介绍Spring中的AOP。mysql
Joinpoint
(链接点):任何能够被加强的方法,都称为链接点。在spring中,这些点指的是方法,由于spring只支持方法类型的链接点。Pointcut
(切入点):将要被加强的方法。即咱们要对哪些Joinpoint进行拦截的定义。Advice
(通知/加强):所谓通知是指拦截到Joinpoint以后所要作的事情就是通知。通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。Introduction
(引介):Adivice是对方法进行加强的,而Introdution是针对类进行加强的Target
(目标对象):被代理的目标对象。Weaving
(织入):是指把加强应用到目标对象来建立新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。Proxy
(代理):一个对象被jdk代理或者cglib代理后的对象,称为代理Aspect
(切面):多个通知和切入点的配置关系。首先是依赖:
web
<!--IOC相关依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.6.RELEASE</version> </dependency> <!--junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> <version>4.12</version> </dependency> <!-- AOP相关依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.0.6.RELEASE</version> </dependency> <!-- spring集合junit的依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.6.RELEASE</version> </dependency>
而后配置xml主配置文件:spring
<?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 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.xsd"> <!-- 待加强的bean --> <bean id="aspectService" class="com.bilibili.service.impl.AspectServiceImpl"></bean> <!-- 加强的功能bean --> <bean id="logger" class="com.bilibili.common.Logger"></bean> <!-- 配置AOP --> <aop:config> <!-- 配置切面 id:惟一标识 ref:引用的通知类(bean id) --> <aop:aspect id="logAdvice" ref="logger"> <!-- 配置前置通知 method:配置通知的方法(即加强的功能) pointcut:配置切面,也就是对哪一个方法进行加强(使用AspectJ表达式) execution:使用AspectJ切入点表达式 --> <aop:before method="printLog" pointcut="execution(public void com.bilibili.service.impl.AspectServiceImpl.update())"></aop:before> </aop:aspect> </aop:config> </beans>
execution表达式的匹配方式:sql
execution:匹配方法的执行(经常使用) execution(表达式) 表达式语法:execution([修饰符] 返回值类型 包名.类名.方法名(参数)) 写法说明: 全匹配方式: public void cn.bilibili.service.impl.AccountServiceImpl.saveAccount(cn.bilibili.domain.Account) 访问修饰符能够省略 void cn.bilibili.service.impl.AccountServiceImpl.saveAccount(cn.bilibili.domain.Account) 返回值可使用*号,表示任意返回值 * cn.bilibili.service.impl.AccountServiceImpl.saveAccount(cn.bilibili.domain.Account) 包名可使用*号,表示任意包,可是有几级包,须要写几个* * *.*.*.*.AccountServiceImpl.saveAccount(cn.bilibili.domain.Account) 使用..来表示当前包,及其子包 * cn..AccountServiceImpl.saveAccount(cn.bilibili.domain.Account) 类名可使用*号,表示任意类 * cn..*.saveAccount(cn.bilibili.domain.Account) 方法名可使用*号,表示任意方法 * cn..*.*( cn.bilibili.domain.Account) 参数列表可使用*,表示参数能够是任意数据类型,可是必须有参数 * cn..*.*(*) 参数列表可使用..表示有无参数都可,有参数能够是任意类型 * cn..*.*(..) 全通配方式: * *..*.*(..) 注: 一般状况下,咱们都是对业务层的方法进行加强,因此切入点表达式都是切到业务层实现类。 execution(* cn.bilibili.service.impl.*.*(..)) 注意:多个execution可使用 || && 链接
<aop:config>
:用于声明开始aop配置<aop:aspect>
:切面id
:给切面提供一个惟一标识。ref
:引用配置好的通知类bean的id<aop:point>
:切点,方便一个切点屡次使用id
:切点的惟一标识expression
:定义切点的表达式<aop:aspect>
:前置通知method
:通知的方法(即加强的功能)pointcut
:AspectJ表达式pointcut-ref
:引用切点(和pointcut不可同时使用)<aop:after-returning>
:后置通知<aop:after-throwing>
:异常通知<aop:after>
:最终通知(至关于finally)<aop:around>
:环绕通知,通常单独使用,该通知(加强的方法)接收一个类型为ProceedingJoinPoint
的参数,该类型有一个proceed()
方法,用来调用被代理方法。在主配置文件中开启包扫描和注解AOP:数据库
<!-- 开启注解扫描的包 --> <context:component-scan base-package="com.bilibili"></context:component-scan> <!-- 开启注解AOP --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
把被代理对象注册到容器中:
express
@Service("accountService") public class AccountServiceImpl implements AccountService { public void update() { System.out.println("更新操做"); } public void save() { System.out.println("保存操做"); } public void delete() { System.out.println("删除操做"); } }
在通知类上添加@Component()
进行注册,@Aspect
表示切面类。方法上添加@Before()
前置通知、@AfterReturning()
后置通知、@AfterThrowing()
异常通知、@After()
最终通知。@Pointcut()
注解空方法表示切面。
编程
@Component("logger") @Aspect//声明当前是一个切面类(通知类) public class Logger { //注解前置通知,value属性就是切点的AspectJ表达式 @Before("execution(* com.bilibili.service.impl.AccountServiceImpl.update())") public void beforePrintLog(){ System.out.println("<aop:before>标签配置前置通知,即加强的功能在目标方法以前"); } //切面 @Pointcut("execution(* com.bilibili.service.impl.AspectServiceImpl.update())") public void pt1() { } //注解后置通知,引用切面 @AfterReturning("pt1()") public void afterReturningPrintLog(){ System.out.println("<aop:after-returning>标签配置后置通知,即加强的功能在目标方法以后"); } //下面就不一个一个标注了。 public void afterThrowingPrintLog(){ System.out.println("<aop:after-throwing>标签配置异常通知,即目标方法出现异常的时候执行"); } public void afterPrintLog(){ System.out.println("<aop:after>标签配置最终通知。即不论是否出现异常,都会执行,相似finally"); } public Object aroundPrintLog(ProceedingJoinPoint pjp){ Object obj = null; try { System.out.println("环绕通知,手动在代码中定义什么时候执行"); obj = pjp.proceed();//目标方法执行 System.out.println("环绕通知,手动在代码中定义什么时候执行"); } catch (Throwable throwable) { System.out.println("环绕通知,手动在代码中定义什么时候执行"); throwable.printStackTrace(); }finally { System.out.println("环绕通知,手动在代码中定义什么时候执行"); } return obj; } }
只需在IoC的纯注解配置类上添加@EnableAspectJAutoProxy()
开启AOP便可。
tomcat
//声明当前类是一个spring的配置类,用来替代xml配置文件 //获取容器时须要使用AnnotationApplicationContext(@Configuration标注的类.class) @Configuration //用于配置容器初始化时须要扫描的包 //和xml配置中<context:component-scan base-package="com.bilibili"/>做用一致 @ComponentScan("com.bilibili") //导入其余配置类 @Import(JdbcConfig.class) //开启AOP @EnableAspectJAutoProxy public class SpringConfig { }
继承该类后能够不用手动获取JdbcTemplate对象。mybatis
使用这种方式没法用注解注入DataSource,只能经过xml注入(注入给子类也能够)
事务处理位于业务层,Spring提供了一个spring-tx包来进行控制事务,事务是基于AOP,原理也比较好理解。
PlatformTransactionManager:平台事务管理器,是Spring真正管理事务的对象,是一个接口,经常使用实现类有以下两个:
Spring主要经过两个重要的接口来描述一个事务:
Spring框架进行事务的管理,首先使用TransactionDefinition对事务进行定义。经过PlatformTransactionManager根据TransactionDefinition的定义信息进行事务的管理。在事务管理过程当中产生一系列的状态:保存到TransactionStatus中。
TransactionDefinition接口具备如下经常使用方法:
String getName()
:获取事务对象名称int getIsolationLevel()
:获取事务隔离级别int getPropagationBehavior()
:获取事务传播行为int getTimeout()
:获取事务超时时间boolean isReadOnly()
获取事务是否只读事务隔离级别:
ISOLATION_DEFAULT
:默认级别,会根据不一样数据库自动变动(MySQL为可重复读,Oracle和Access为读已提交)ISOLATION_READ_UNCOMMITTED
:读未提交(会产生脏读)ISOLATION_READ_COMMITTED
:读已提交(解决脏读)ISOLATION_REPEATABLE_READ
:可重复读ISOLATION_SERIALIZABLE
:串行化事务的传播行为
传播行为解决的问题: 一个业务层事务 调用 另外一个业务层事务时,事务之间关系如何处理
事务传播行为PROPAGATION的取值: REQUIRED 支持当前事务,若是不存在,就新建一个(默认的传播行为) * 删除客户 删除订单, 处于同一个事务,若是 删除订单失败,删除客户也要回滚 SUPPORTS 支持当前事务,若是不存在,就不使用事务 MANDATORY 支持当前事务,若是不存在,抛出异常 REQUIRES_NEW 若是有事务存在,挂起当前事务,建立一个新的事务 * 生成订单, 发送通知邮件, 通知邮件会建立一个新的事务,若是邮件失败, 不影响订单生成 NOT_SUPPORTED 以非事务方式运行,若是有事务存在,挂起当前事务 NEVER 以非事务方式运行,若是有事务存在,抛出异常 NESTED 若是当前事务存在,则嵌套事务执行 * 依赖于 JDBC3.0 提供 SavePoint 技术 * 删除客户 删除订单, 在删除客户后, 设置SavePoint, 执行删除订单,删除订单和删除客户在同一个事务 ,删除部分订单失败, 事务回滚 SavePoint , 由用户控制是事务提交 仍是 回滚 三个表明: REQUIRED 一个事务, 要么都成功,要么都失败 REQUIRES_NEW 两个不一样事务,彼此之间没有关系 一个事务失败了 不影响另外一个事务 NESTED 一个事务, 在A事务 调用 B过程当中, B失败了, 回滚事务到 以前SavePoint , 用户能够选择提交事务或者回滚事务
超时时间:默认值是-1,没有超时限制。若是有,以秒为单位进行设置。
是不是只读事务:建议查询时设置为只读。
TransactionStatus:事务的运行状态,经常使用方法以下:
void flush()
:刷新事务boolean hasSavePoint()
:是否存在存储点boolean idComplated()
:事务是否完成boolean isNewTransaction()
:是否为新事物boolean isRollbackOnly()
:事务是否回滚void setRollbackOnly()
设置事务回滚首先添加依赖:
主要是spring-tx、spring-aspects这两个不要漏了。
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.0.6.RELEASE</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.38</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.9</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.6.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.0.6.RELEASE</version> </dependency>
配置事务管理器的bean
<!-- bean的名字叫作transactionManager,由于在配置事务策略的时候须要指定的事务管理器的默认名字就是transactionManager,若是是其余名字,在配置事务策略的时候,须要手动指定。 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean>
配置事务策略:
<!-- 配置事务策略 --> <tx:advice id="tx"> <tx:attributes> <!-- 指定对那些方法使用事务 name:须要进行事务管理的方法名 *表明全部方法,这里须要填方法的名字便可,不是aspectj那种包名加类名方法名。 isolation:事务隔离级别 propagation:事务传播行为 timeout:超时时间 ready-only:设置事务是否只读 rollback-for:指定对哪一种异常进行回滚 no-rollback-for:指定对那种异常不进行回滚 --> <tx:method name="*" /> </tx:attributes> </tx:advice>
配置事务AOP:
<!-- 配置aop --> <aop:config> <aop:pointcut id="pt1" expression="execution(* com.bilibili.service.impl.*.*(..))"></aop:pointcut> <!-- 配置事务策略运用到事务管理器 --> <aop:advisor advice-ref="tx" pointcut-ref="pt1"></aop:advisor> </aop:config>
在spring主配置文件中:
<!-- 开启spring的注解扫描 --> <context:component-scan base-package="com.bilibili"></context:component-scan> <!-- 开启事务的注解扫描 --> <tx:annotation-driven></tx:annotation-driven>
而后只在方法或者类或者接口上配置@Transactional
便可开启事务
在配置类上添加@EnableTransactionManagement
开启注解事务管理:
@Configuration //声明当前是一个配置类,用来代替applicationContext.xml文件 @ComponentScan("com.bilibili") //开启注解包扫描 @PropertySource("classpath:jdbc.properties") // 加载外部配置文件 @EnableTransactionManagement // 开启注解事务管理 public class SpringConfig { @Value("${jdbc.url}")//引入外部配置文件中的资源 private String url; @Value(("${jdbc.driverClass}")) private String driverClass; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; @Bean("dataSource")//将bean装配到spring容器中 public DataSource getDataSource(){ DruidDataSource dataSource = new DruidDataSource(); dataSource.setUrl(url); dataSource.setDriverClassName(driverClass); dataSource.setUsername(username); dataSource.setPassword(password); return dataSource; } @Bean("jdbcTemplate") public JdbcTemplate getJdbcTemplate(@Qualifier("dataSource") DataSource dataSource){ JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); return jdbcTemplate; } @Bean("transactionManager") public DataSourceTransactionManager getDataSourceTransactionManager(@Qualifier("dataSource") DataSource dataSource){ DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(dataSource); return dataSourceTransactionManager; } }
在Tomcat中,因为Servlet是Tomcat建立的,没法放入Spring中,当Servlet须要使用Service的时候是不太方便的,此时就可使用监听器来自动建立ApplicationContext。
spring监听器原理:监听servletContext建立,建立ApplicationContext并将其放入上下文域。
本身实现:
建立一个servletContext监听器
@WebListener() public class MyListener implements ServletContextListener { /** * 在ServletContext对象建立的时候,建立spring容器。 * 1.建立spring容器,须要配置文件的名字,名字并非固定的,因此能够配置在web.xml中 * 2.spring容器建立以后,须要可以被全部的servlet来使用,那么须要将spring容器保存起来,保存到哪里?ServletContext域对象中 * */ @Override public void contextInitialized(ServletContextEvent servletContextEvent) { //获取servletContext域对象 ServletContext servletContext = servletContextEvent.getServletContext(); //读取web.xml中的配置参数 -- 即spring的核心配置文件的名字 String contextConfig = servletContext.getInitParameter("contextConfig"); //建立spring容器 ApplicationContext ac = new ClassPathXmlApplicationContext(contextConfig); //将spring容器保存到servletContext对象中 servletContext.setAttribute("ac",ac); } @Override public void contextDestroyed(ServletContextEvent servletContextEvent) { } }
web.xml中配置spring配置文件位置:
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <!-- 配置spring核心配置文件的名字 --> <context-param> <param-name>contextConfig</param-name> <param-value>applicationContext.xml</param-value> </context-param> </web-app>
而后在servlet中就能够获取servletContext中保存的ApplicationContext了。
使用Spring的监听器:
引入依赖:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.0.6.RELEASE</version> </dependency>
web.xml中配置spring主文件位置和监听器:
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <!-- spring核心配置文件的位置 key:是固定的 value:格式固定,classpath:文件名 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <!-- 告诉tomcat 用于建立spring容器的监听器的位置 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> </web-app>
而后就能够在servlet中使用下面的方式获取ApplicationContext:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ApplicationContext ac = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext()); userService = (UserService ) ac.getBean("userService"); userService.register(); }
其实用了SpringMVC以后不会这么麻烦23333🤣