一、Aop【Aspect Oriented Programming】(面向切面编程)
1、概述:
通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
优点:①各个步骤之间的良好隔离性;②源代码无关性 。
缺点:难用。你需要实现大量的接口,继承大量的类。
2、主要功能:日志记录,性能统计,安全控制,事务处理,异常处理等等。
3、主要意图:将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
4、Aop实现原理:①通过代理机制实现;②接口+实现类:可以采用jdk的proxy来生成代理对象;③有接口的实现类和没接口的实现类都可以采用cglib字节码增强 来实现代理。
5、Aop 术语
①通知(Advice):想要实现的功能,也就是上面说的 安全,事物,日志等。
②连接点(JoinPoint):就是spring允许使用通知的地方,基本每个方法的前,后(两者都有也行),或抛出异常时都可以是连接点,spring只支持方法连接点.其他如aspectJ还可以让你在构造器或属性注入时都行。
③切入点(Pointcut):在调用某几个方法之前,之后或者抛出异常时干点什么,那么就用切入点来定义这几个方法,让切入点来筛选连接点,选中那几个你想要的方法。
④切面(Aspect):切面是通知和切入点的结合。(没连接点什么事情,连接点就是为了让你好理解切点,搞出来的,明白这个概念就行了。)通知说明了干什么和什么时候干(什么时候通过方法名中的before,after,around等就能知道),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。(什么是切面?用刀把一个西瓜分成两瓣,切开的切口就是切面;炒菜,锅与炉子共同来完成炒菜,锅与炉子就是切面。web层级设计中,web层->网关层->服务层->数据层,每一层之间也是一个切面。编程中,对象与对象之间,方法与方法之间,模块与模块之间都是一个个切面。)
⑤引入(introduction): 允许我们向现有的类添加新方法属性。相当于把切面(也就是新方法属性:通知定义的)用到目标类中。
⑥目标(target):引入中所提到的目标类,也就是要被通知的对象,也就是真正的业务逻辑,它可以在毫不知情的情况下,被咱们织入切面。而自己专注于业务本身的逻辑。
⑦代理(proxy):它允许Spring动态生成一个新类来实现必要的接口,织入通知,并且把对这些接口的任何调用都转发到目标类。
⑧织入(weaving):把切面应用到目标对象来创建新的代理对象的过程
6、spring用代理类包裹切面,把他们织入到Spring管理的bean中。也就是说代理类伪装成目标类,它会截取对目标类中方法的调用,让调用者对目标类的调用都先变成调用伪装类,伪装类中就先执行了切面,再把调用转发给真正的目标bean。
现在可以自己想一想,怎么搞出来这个伪装类,才不会被调用者发现(过JVM的检查,JAVA是强类型检查,哪里都要检查类型)。
(1) 实现和目标类相同的接口,我也实现和你一样的接口,反正上层都是接口级别的调用,这样我就伪装成了和目标类一样的类(实现了同一接口,咱是兄弟了),也就逃过了类型检查,到java运行期的时候,利用多态的后期绑定(所以spring采用运行时),伪装类(代理类)就变成了接口的真正实现,而他里面包裹了真实的那个目标类,最后实现具体功能的还是目标类,只不过伪装类在之前干了点事情(写日志,安全检查,事务等)。
这就好比,一个人让你办件事,每次这个时候,你弟弟就会先出来,当然他分不出来了,以为是你,你这个弟弟虽然办不了这事,但是他知道你能办,所以就答应下来了,并且收了点礼物(写日志),收完礼物了,给把事给人家办了啊,所以你弟弟又找你这个哥哥来了,最后把这是办了的还是你自己。但是你自己并不知道你弟弟已经收礼物了,你只是专心把这件事情做好。
顺着这个思路想,要是本身这个类就没实现一个接口呢,你怎么伪装我,我就压根没有机会让你搞出这个双胞胎的弟弟,那么就用第2种代理方式,创建一个目标类的子类,生个儿子,让儿子伪装我
(2)生成子类调用,这次用子类来做为伪装类,当然这样也能逃过JVM的强类型检查,我继承的吗,当然查不出来了,子类重写了目标类的所有方法,当然在这些重写的方法中,不仅实现了目标类的功能,还在这些功能之前,实现了一些其他的(写日志,安全检查,事物等)。
这次的对比就是,儿子先从爸爸那把本事都学会了,所有人都找儿子办事情,但是儿子每次办和爸爸同样的事之前,都要收点小礼物(写日志),然后才去办真正的事。当然爸爸是不知道儿子这么干的了。这里就有件事情要说,某些本事是爸爸独有的(final的),儿子学不了,学不了就办不了这件事,办不了这个事情,自然就不能收人家礼了。
前一种兄弟模式,spring会使用JDK的java.lang.reflect.Proxy类,它允许Spring动态生成一个新类来实现必要的接口,织入通知,并且把对这些接口的任何调用都转发到目标类。
后一种父子模式,spring使用CGLIB库生成目标类的一个子类,在创建这个子类的时候,spring织入通知,并且把对这个子类的调用委托到目标类。
相比之下,还是兄弟模式好些,他能更好的实现松耦合,尤其在今天都高喊着面向接口编程的情况下,父子模式只是在没有实现接口的时候,也能织入通知,应当做一种例外。
7、cglib(增强字节码)
①没有接口,只有实现类;②采用字节码增强框架cglib,在运行时创建目标类的子类,从而对目标类进行增强。③导入架包:核心包hibernate-distribution-3.6.10.Final\lib\bytecode\cglib\cglib-2.2.jar和依赖包struts-2.3.15.3\apps\struts2-blank\WEB-INF\lib\asm-3.3.jar
spring-core.jar已经整合上面的两个包了。
8、AOP联盟
①前置通知(Before Advice):在目标方法执行前实施增强。 在连接点之前执行的Advice,不过除非它抛出异常,否则没有能力中断执行流。使用 @Before
注解使用这个Advice。
②后置通知(After Advice):在目标方法执行后实施增强。 无论连接点是通过什么方式退出的(正常返回或者抛出异常)都会执行在结束后执行这些Advice。通过 @After
注解使用。
③环绕通知(Around Advice):在目标方法执行、后前实施增强。围绕连接点执行的Advice,就你一个方法调用。这是最强大的Advice。通过 @Around
注解使用。
④异常抛出通知(AfterThrowing Advice):在方法抛出异常后实施增强。如果一个方法通过抛出异常来退出的话,这个Advice就会被执行。通用 @AfterThrowing
注解来使用。
9、AspectJ
①它是一个基于java语言的AOP框架;②兼容Java平台,可以无缝扩展。spring2.0以后新增了对切点表达式的支持。③易学易用。@AspectJ是AspectJ1.5新增功能,通过JDK5注解技术,允许直接在Bean类中定义切面。(新版本Spring建议使用AspectJ方式来开发AOP,主要用途:自定义开发)
AspectJ与 Spring Aop的区别: Spring Aop采用的动态织入,而Aspectj是静态织入。静态织入:指在编译时期就织入,即:编译出来的class文件,字节码就已经被织入了。动态织入又分静动两种,静则指织入过程只在第一次调用时执行;动则指根据代码动态运行的中间状态来决定如何操作,每次调用Target的时候都执行。
10、切点表达式
语法:execution(修饰符 返回值 包.类.方法名(参数)throws 异常)
execution(* com.nic.* .*(..))
修饰符:一般省略
第一个*代表所有的返回值类型 :不能省略 。void 表示返回没有值 、 String 表示返回字符串 、 * 表示 任意值
包 : com.nic 固定包
第二个*代表所有的类
第三个*代表类所有方法
最后一个..代表所有的参数
11、JdbcTemplate
(1)简介:①JdbcTemplate类似于人DBUtils,用于操作Jdbc的工具类,它需要依赖于连接池,DataSource(数据源);②JDBC(java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的java API;③ODBC(Open Database Connectivity)开放数据库连接,是微软公司开源提供的一组数据库访问的标准API(应用编程接口)(常用于SQLserver);④DBCP(DataBase Connection Pool)数据库连接池,是java数据库连接池的一种,由Apache开发;⑤C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,spring等。
(2)C3P0和dbcp的区别:①dbcp没有自动回收空闲连接功能, C3P0有;②两者主要是对数据连接的处理方式不同!C3P0提供最大空闲时间,DBCP提供最大连接数;③前者当连接超过最大空闲连接时间时,当前连接就会被断掉。DBCP当连接数超过最大连接数时,所有连接都会被断开;④dbcp它的原理是维护多个连接对象Connection,在web项目要连接数据库时直接使用它维护的对象进行连接,省去每次都要创建连接对象的麻烦。提高效率和减少内存使用。s3p0可以自动回收连接,dbcp需要自己手动释放资源返回。不过dbcp效率比较高。
(3)导入架包:①com.springsource.com.mchange.v2.c3p0-0.9.1.2.jar(c3p0连接池)/
com.springsource.org.apache.commons.dbcp-1.2.2.osgi.jar、com.springsource.org.apache.commons.pool-1.5.3jar(c3p0连接池和dbcp连接池选择一种导入就行);②mysql-connector-java-5.0.8-bin.jar(mysql驱动) 、③spring-jdbc-3.2.0.RELEASE.jar;④spring-tx-3.2.0.RELEASE.jar(事务)。
(4)简单JdbcTemplate应用
①model层
package com.gfy.model;
public class User {
private Integer id;
private String username;
private String password;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
②创建数据库:
③test层
package com.gfy.test;
import org.apache.commons.dbcp.BasicDataSource;
import org.junit.Test;
import org.springframework.jdbc.core.JdbcTemplate;
public class lesson04 {
@Test
public void test() throws Exception {
//创建数据源
BasicDataSource dataSource=new BasicDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///spring_day04");// 其中的///代表本地,spring_day04表示你的数据库中的表名字
dataSource.setUsername("root");//root表示你的数据库的登录名
dataSource.setPassword("123456");//123456表示你你的数据库的登录密码
//创建JdbcTemplate
JdbcTemplate jdbcTemplate=new JdbcTemplate(dataSource);
jdbcTemplate.update("update t_user set username =? where id=?","aa",1);//表示将id=1的username修改为zzz
System.out.println("成功更新!");
}
}
结果:
(5)数据源由spring配置
①dao层:接口
package com.gfy.dao;
import com.gfy.model.User;
public interface IUserDao {
public void add(User user);
}
实现类:
package com.gfy.dao;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.stereotype.Repository;
import com.gfy.model.User;
@Repository //Dao层
public class UserDaoImpl2 implements IUserDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public void add(User user) {
System.out.println("Dao添加用户"+user);
jdbcTemplate.update("insert t_user (username,password) values(?,?) ",user.getUsername(),user.getPassword());
}
}
②model层
package com.gfy.model;
public class User {
private Integer id;
private String username;
private String password;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
③web层,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: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.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置DBCP dataSource对象 -->
<!-- BasicDataSource dataSource=new BasicDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///spring_day04");// 其中的///代表本地
dataSource.setUsername("root");
dataSource.setPassword("123456"); -->
<!-- <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql:///spring_day04" />
<property name="username" value="root" />
<property name="password" value="123456" />
</bean>
-->
<!-- 配置C3P0数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql:///spring_day04" />
<property name="user" value="root" />
<property name="password" value="123456" />
</bean>
<!-- 配置jdbcTemplate对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置Dao -->
<bean id="userDao" class="com.gfy.dao.UserDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
</beans>
④test层
package com.gfy.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.gfy.dao.IUserDao;
import com.gfy.model.User;
public class lesson05 {
@Test
public void test() throws Exception {
/*
//创建数据源
*
BasicDataSource dataSource=new BasicDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///spring_day04");// 其中的///代表本地
dataSource.setUsername("root");
dataSource.setPassword("123456");
//创建JdbcTemplate
JdbcTemplate jdbcTemplate=new JdbcTemplate(dataSource);
jdbcTemplate.update("update t_user set username ? where id=?","zzz",1);
*/
ApplicationContext context=new ClassPathXmlApplicationContext("beans05.xml");
IUserDao userDao=(IUserDao) context.getBean("userDao");
User user=new User();
user.setUsername("gfrfy");
user.setPassword("123657");
userDao.add(user);
}
}
(6)用JdbcDaoSupport简化JdbcTemplate和配置db.properties(数据库已存在)
把数据库的连接信息配置到一个独立的文件中;在src[类路径]写个db.properties文件,然后在xml中配置读取db.properties文件信息。
①配置db.properties
driverClass=com.mysql.jdbc.Driver
jdbcUrl=jdbc:mysql:///spring_day04
user=root
password=123456
②model层
package com.gfy.model;
public class User {
private Integer id;
private String username;
private String password;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
③dao层:接口
package com.gfy.dao;
import com.gfy.model.User;
public interface IUserDao {
public void add(User user);
}
实现类:
package com.gfy.dao;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.stereotype.Repository;
import com.gfy.model.User;
@Repository //Dao层
public class UserDaoImpl extends JdbcDaoSupport implements IUserDao {
@Override
public void add(User user) {
System.out.println("Dao添加用户"+user);
getJdbcTemplate().update("insert t_user (username,password) values(?,?) ",user.getUsername(),user.getPassword());
}
}
④web层,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: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.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置DBCP dataSource对象 -->
<!-- BasicDataSource dataSource=new BasicDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///spring_day04");// 其中的///代表本地
dataSource.setUsername("root");
dataSource.setPassword("123456"); -->
<!-- <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql:///spring_day04" />
<property name="username" value="root" />
<property name="password" value="123456" />
</bean>
-->
<!-- 读取db.properties文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置C3P0数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${driverClass}" />
<property name="jdbcUrl" value="${jdbcUrl}" />
<property name="user" value="${user}" />
<property name="password" value="${password}" />
</bean>
<!-- 配置jdbcTemplate对象 -->
<!--
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean> -->
<!-- 配置Dao -->
<bean id="userDao" class="com.gfy.dao.UserDaoImpl">
<!-- <property name="jdbcTemplate" ref="jdbcTemplate"></property>-->
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
⑤test层
package com.gfy.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.gfy.dao.IUserDao;
import com.gfy.model.User;
public class lesson06 {
@Test
public void test() throws Exception {
ApplicationContext context=new ClassPathXmlApplicationContext("beans06.xml");
IUserDao userDao=(IUserDao) context.getBean("userDao");
User user=new User();
user.setUsername("xiaomi");
user.setPassword("16700");
userDao.add(user);
System.out.println("成功插入用户!");
}
}