三、AOP
1. 底层原理
面向切面编程,利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。通俗来说就是在不修改代码的情况下添加新的功能。
底层通过动态代理来实现:
- 第一种:有接口的情况,使用JDK动态代理:创建接口实现类的代理对象。
- 第二种:无接口的情况,使用CGLIB动态代理:创建当前类子类的代理对象。 JDK动态代理举例:
通过 java.lang.reflect.Proxy类 的 newProxyInstance方法 创建代理类。
newProxyInstance方法:
- 参数一:类加载器
- 参数二:所增强方法所在的类,这个类实现的接口,支持多个接口
- 参数三:实现InvocationHandle接口,重写invoke方法来添加新的功能
代码举例:
public interface UserDao { public int add(int a, int b); public int multi(int a, int b); }
public class UserDaoImpl implements UserDao { @Override public int add(int a, int b) { return a+b; } @Override public int multi(int a, int b) { return a*b; } }
public class Main { @Test public void test1(){ //所需代理的类实现的接口,支持多个接口 Class[] interfaces = {UserDao.class}; UserDao userDao = new UserDaoImpl(); //调用newProxyInstance方法来创建代理类 UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(Main.class.getClassLoader(), interfaces, new UserDaoProxy(userDao)); int result = userDaoProxy.add(1, 2); System.out.println(result); } //创建内部类,实现InvocationHandler接口,重写invoke方法,添加新功能 class UserDaoProxy implements InvocationHandler { Object obj; //通过有参构造函数将所需代理的类传过来 public UserDaoProxy(Object obj){ this.obj = obj; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("进入" + method.getName() + "方法,这是新增的代码,参数有" + Arrays.toString(args)); //执行原有的代码 Object invoke = method.invoke(obj, args); System.out.println("方法原先的内容执行完了"); return invoke; } } }
2. 基于AspectJ实现AOP操作
(1)AOP相关术语:
- 连接点:类中可以被增强的方法,称为连接点。
- 切入点:实际被增强的方法,称为切入点。
- 通知:增强的那一部分逻辑代码。通知有多种类型:
- 前置通知:增强部分代码在原代码前面。
- 后置通知:增强部分代码在原代码后面。
- 环绕通知:增强部分代码既有在原代码前面,也有在原代码后面。
- 异常通知:原代码发生异常后才会执行。
- 最终通知:类似与finally那一部分
- 切面:指把通知应用到切入点这一个动作。
(2)基于AspectJ实现AOP有两种方式:
基于xml配置文件 基于注解方法
(3)切入点表达式
语法:execution([权限修饰符] [返回类型] [类全路径] [方法名称] [参数列表])
举例1:对 com.atguigu.dao.BookDao 类里面的 add 进行增强
execution(* com.auguigu.dao.BookDao.add(..))
举例2:对 com.atguigu.dao.BookDao 类里面的所有的方法进行增强
execution(* com.atguigu.dao.BookDao.*(..))
举例 3:对 com.atguigu.dao 包里面所有类,类里面所有方法进行增强
execution(* com.atguigu.dao.*.* (..))
(1)基于注解方式
@Component public class User { public void add(){ System.out.println("User.add()"); } }
@Component @Aspect //使用Aspect注解 public class UserProxy { //前置通知 @Before(value="execution(* com.oymn.spring5.User.add(..))") public void before(){ System.out.println("UserProxy.before()"); } //后置通知 @AfterReturning(value="execution(* com.oymn.spring5.User.add(..))") public void afterReturning(){ System.out.println("UserProxy.afterReturning()"); } //最终通知 @After(value="execution(* com.oymn.spring5.User.add(..))") public void After(){ System.out.println("UserProxy.After()"); } //异常通知 @AfterThrowing(value="execution(* com.oymn.spring5.User.add(..))") public void AfterThrowing(){ System.out.println("UserProxy.AfterThrowing()"); } //环绕通知 @Around(value="execution(* com.oymn.spring5.User.add(..))") public void Around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{ System.out.println("UserProxy.Around() _1"); //调用proceed方法执行原先部分的代码 proceedingJoinPoint.proceed(); System.out.println("UserProxy.Around() _2"); } }
配置xml文件:
<!--开启组件扫描--> <context:component-scan base-package="com.oymn"></context:component-scan> <!--开启AspectJ生成代理对象--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
测试类:
@Test public void test2(){ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml"); User user = context.getBean("user", User.class); user.add(); }
运行结果中没有出现异常通知,在add方法中添加int i = 1/0;
public void add(){ int i = 1/0; System.out.println("User.add()"); }
运行结果:从这里也可以看到,但出现异常时,After最终通知有执行,而AfterReturning后置通知并没有执行。
对于上面的例子,有很多通知的切入点都是相同的方法,因此,可以将该切入点进行抽取:通过@Pointcut注解
@Pointcut(value="execution(* com.oymn.spring5.User.add(..))") public void pointDemo(){ } //前置通知 @Before(value="pointDemo()") public void before(){ System.out.println("UserProxy.before()"); }
设置增强类优先级:
当有多个增强类对同一方法进行增强时,可以通过 @Order(数字值)来设置增强类的优先级,数字越小优先级越高。
@Aspect @Order(1) public class PersonProxyva @Component @Aspect @Order(1) public class PersonProxy
完全注解开发 :
可以通过配置类来彻底摆脱xml配置文件:
@Configuration @ComponentScan(basePackages = "com.oymn.spring5") //@EnableAspectJAutoProxy注解相当于上面xml文件中配置的 <aop:aspectj-autoproxy></aop:aspectj-autoproxy> @EnableAspectJAutoProxy(proxyTargetClass = true) public class Config { }
(2)基于xml方式
这种方式开发中不怎么用,了解即可。
创建Book和BookProxy类
public class Book { public void buy(){ System.out.println("buy()"); } }
public class BookProxy { public void before(){ System.out.println("before()"); } }
配置xml文件:
<!--创建对象--> <bean id="book" class="com.oymn.spring5.Book"></bean> <bean id="bookProxy" class="com.oymn.spring5.BookProxy"></bean> <aop:config> <!--切入点--> <aop:pointcut id="p" expression="execution(* com.oymn.spring5.Book.buy(..))"/> <!--配置切面--> <aop:aspect ref="bookProxy"> <aop:before method="before" pointcut-ref="p"/> <!--将bookProxy中的before方法配置为切入点的前置通知--> </aop:aspect> </aop:config>
四、JdbcTemplate
Spring对JDBC进行封装,使用JdbcTemplate方便对数据库的操作。 (1)增删改操作:
int update(String sql, Object... args);
(2)查询:返回某个值
T queryForObject(String sql,Class<T> requiredType);
(3)查询:返回某个对象
T queryForObject(String sql,RowMapper<T> rowMapper,Object ... args);
(4)查询:返回集合
List<T> query(String sql,RowMapper<T> rowMapper,Object... args);
(5)批量增删改:
int[] batchUpdate(String sql,List<Object[]> batchArgs);
举例:
引入相关jar包
配置数据库连接池;配置JdbcTemplate对象
<context:component-scan base-package="com.oymn"></context:component-scan> <!--配置数据库连接池 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"> <property name="url" value="jdbc:mysql://localhost:3306/book" /> <property name="username" value="root" /> <property name="password" value="000000" /> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> </bean> <!--创建JdbcTemplate对象--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!--注入数据库连接池--> <property name="dataSource" ref="dataSource"></property> </bean>
创建Service类和Dao类,在Dao类中注入JdbcTemplate对象
public interface BookDao { public void add(Book book); //添加图书 public void update(Book book); //修改图书 public void delete(int id); //删除图书 public int queryCount(); //查询数量 public Book queryBookById(int id); //查询某本书 public List<Book> queryBooks(); //查询所有书 public void batchAddBook(List<Object[]> books); //批量添加图书 public void batchUpdateBook(List<Object[]> books); //批量修改图书 public void batchDeleteBook(List<Object[]> args); //批量删除图书 }
@Repository public class BookDaoImpl implements BookDao { @Autowired private JdbcTemplate jdbcTemplate; @Override public void add(Book book) { String sql = "insert into t_book set name=?,price=?"; Object[] args = {book.getBookName(),book.getBookPrice()}; int update = jdbcTemplate.update(sql, args); System.out.println(update); } @Override public void update(Book book) { String sql = "update t_book set name=?,price=? where id=?"; Object[] args = {book.getBookName(),book.getBookPrice(),book.getBookId()}; int update = jdbcTemplate.update(sql, args); System.out.println(update); } @Override public void delete(int id) { String sql = "delete from t_book where id=?"; int update = jdbcTemplate.update(sql, id); System.out.println(update); } @Override public int queryCount() { String sql = "select count(*) from t_book"; Integer count = jdbcTemplate.queryForObject(sql, Integer.class); return count; } @Override public Book queryBookById(int id) { String sql = "select id bookId,name bookName,price bookPrice from t_book where id=?"; Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id); return book; } @Override public List<Book> queryBooks() { String sql = "select id bookId,name bookName,price bookPrice from t_book"; List<Book> bookList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class)); return bookList; } @Override public void batchAddBook(List<Object[]> books) { String sql = "insert into t_book set id=?,name=?,price=?"; int[] ints = jdbcTemplate.batchUpdate(sql, books); System.out.println(ints); } @Override public void batchUpdateBook(List<Object[]> books) { String sql = "update t_book set name=?,price=? where id=?"; int[] ints = jdbcTemplate.batchUpdate(sql, books); System.out.println(ints); } @Override public void batchDeleteBook(List<Object[]> args) { String sql = "delete from t_book where id=?"; int[] ints = jdbcTemplate.batchUpdate(sql, args); System.out.println(ints); } }
@Service public class BookService { @Autowired private BookDao bookDao = new BookDaoImpl(); //添加图书 public void add(Book book){ bookDao.add(book); } //修改图书 public void update(Book book){ bookDao.update(book); } //删除图书 public void delete(Integer id){ bookDao.delete(id); } //查询数量 public int queryCount(){ return bookDao.queryCount(); } //查询图书 public Book queryBookById(Integer id){ return bookDao.queryBookById(id); } //查询所有图书 public List<Book> queryBooks(){ return bookDao.queryBooks(); } //批量添加图书 public void batchAddBook(List<Object[]> books){ bookDao.batchAddBook(books); } //批量修改图书 public void batchUpdateBook(List<Object[]> books){ bookDao.batchUpdateBook(books); } //批量删除图书 public void batchDeleteBook(List<Object[]> args){ bookDao.batchDeleteBook(args); } }
五、事务管理
事务是数据库操作最基本单位,要么都成功,要么都失败。
典型场景:转账
事务四个特性ACID:原子性,一致性,隔离性,持久性。
Spring事务管理有两种方式:编程式事务管理 和 声明式事务管理,一般使用声明式事务管理,底层使用AOP原理。
声明式事务管理有两种方式:基于xml配置方式 和 基于注解方式,一般使用注解方式。
Spring事务管理提供了一个接口,叫做事务管理器,这个接口针对不同的框架提供不同的实现类。
对于使用JdbcTemplate进行数据库交互,则使用DataSourceTransactionManager实现类,如果整合Hibernate框架则使用HibernateTransactionManager实现类,具体情况具体使用。
(1)注解实现声明式事务管理:
<!-- 数据库连接池 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"> <property name="url" value="jdbc:mysql://localhost:3306/book" /> <property name="username" value="root" /> <property name="password" value="000000" /> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> </bean> <!--创建JdbcTemplate对象--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!--注入数据库连接池--> <property name="dataSource" ref="dataSource"></property> </bean> <!--创建事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!--开启事务注解--> <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
在service类上面或者service类的方法上面添加事务注解@Transactional
如果把@Transactional添加在类上面,这个类里面所有方法都添加事务。 如果只是添加在方法上面,则只为这个方法添加事务。
@Service @Transactional public class UserService {}
声明式事务管理的参数配置:
propagation:事务传播行为,总共有7种,这一块讲的不是很清楚
isolation:事务隔离级别
有三个读问题:脏读,不可重复读,虚读(幻读)。
设置隔离级别,解决读问题:
脏读 不可重复读 虚读 READ UNCOMMITED(读未提交) 有 有 有 READ COMMITED(读已提交) 无 有 有 REPEATABLE READ(可重复读) 无 无 有 SERIALIZABLE(串行化) 无 无 无 timeout:超时时间
事务需要在一定时间内进行提交,超过时间后回滚。 默认值是-1,设置时间以秒为单位。 readOnly:是否只读 默认值为false,表示可以查询,也可以增删改。 设置为true,只能查询。 rollbackFor:回滚,设置出现哪些异常进行事务回滚。
noRollbackFor:不回滚,设置出现哪些异常不进行事务回滚。
@Service @Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.READ_COMMITTED) public class AccountService {}
完全注解实现声明式事务管理:
配置类:
@Configuration //配置类 @ComponentScan(basePackages = "com.oymn.spring5") //开启组件扫描 @EnableTransactionManagement //开启事务 public class Config { //创建数据库连接池 @Bean public DruidDataSource getDruidDataSource(){ DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setDriverClassName("com.mysql.jdbc.Driver"); druidDataSource.setUrl("jdbc:mysql://localhost:3306/book"); druidDataSource.setUsername("root"); druidDataSource.setPassword("000000"); return druidDataSource; } //创建JdbcTemplate对象 @Bean public JdbcTemplate getJdbcTemplate(DataSource dataSource){ JdbcTemplate jdbcTemplate = new JdbcTemplate(); jdbcTemplate.setDataSource(dataSource); return jdbcTemplate; } //创建事务管理器 @Bean public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){ DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource); return transactionManager; } }
@Service public class AccountService { @Autowired private AccountDao accountDao; @Transactional public void accountMoney(){ accountDao.add(); //int i=1/0; //用来模拟转账失败 accountDao.reduce(); } }
(2)xml实现声明式事务管理:
<context:component-scan base-package="com.oymn"></context:component-scan> <!-- 数据库连接池 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"> <property name="url" value="jdbc:mysql://localhost:3306/book" /> <property name="username" value="root" /> <property name="password" value="000000" /> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> </bean> <!--创建JdbcTemplate对象--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!--注入数据库连接池--> <property name="dataSource" ref="dataSource"></property> </bean> <!--创建事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!--配置事务通知--> <tx:advice id="txadvice"> <!--配置事务参数--> <tx:attributes> <tx:method name="accountMoney" propagation="REQUIRED" /> </tx:attributes> </tx:advice> <!--配置切入点和切面--> <aop:config> <!--配置切入点--> <aop:pointcut id="pt" expression="execution(* com.oymn.spring5.Service.*.*(..))"/> <!--配置切面--> <aop:advisor advice-ref="txadvice" pointcut-ref="pt"/> </aop:config>
六、Spring5新特性
- 自带了日志封装 Spring5移除了Log4jConfigListener,官方建议使用Log4j2 Spring5整合Log4j2:
第一步:引入jar包
第二步:创建log4j2.xml配置文件
<?xml version="1.0" encoding="UTF-8"?> <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --> <!--Configuration后面的status用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,可以看到log4j2内部各种详细输出--> <configuration status="INFO"> <!--先定义所有的appender--> <appenders> <!--输出日志信息到控制台--> <console name="Console" target="SYSTEM_OUT"> <!--控制日志输出的格式--> <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> </console> </appenders> <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效--> <!--root:用于指定项目的根日志,如果没有单独指定Logger,则会使用root作为默认的日志输出--> <loggers> <root level="info"> <appender-ref ref="Console"/> </root> </loggers> </configuration>
- @Nullable注解 @Nullable注解可以用在方法上,属性上,参数上,表示方法返回值可以为空,属性可以为空,参数可以为空。
@Nullable //表示方法返回值可以为空 public int getId(); @Nullable //表示参数可以为空 public void setId(@Nullable int Id); @Nullable //表示属性可以为空 public int id;
- 支持函数式风格编程 这是因为java8新增了lamda表达式
@Test public void test() { //1 创建 GenericApplicationContext 对象 GenericApplicationContext context = new GenericApplicationContext(); //2 调用 context 的方法对象注册 context.refresh(); context.registerBean("user1",User.class,() -> new User()); //3 获取在 spring 注册的对象 // User user = (User)context.getBean("com.atguigu.spring5.test.User"); User user = (User)context.getBean("user1"); System.out.println(user); }
- 支持整合JUnit5 (1)整合JUnit4:
第一步:引入jar包
第二步:创建测试类,使用注解方式完成
@RunWith(SpringJUnit4ClassRunner.class) //单元测试框架 @ContextConfiguration("classpath:bean4.xml") //加载配置文件 public class JUnitTest { @Autowired public User user; @Test public void test(){ System.out.println(user); } }
bean4.xml:
<context:component-scan base-package="com.oymn"></context:component-scan>
通过使用@ContextConfiguration注解,测试方法中就不用每次都通过context来获取对象了,比较方便。
ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml"); BookService bookService = context.getBean("bookService",BookService.class);