目录
1 手写spring框架
2 Spring IoC注解式开发
2.1 回顾注解
2.2 声明Bean的注解
2.3 Spring注解的使用
2.4 选择性实例化Bean
2.5 负责注入的注解
2.5.1 @Value
2.5.2 @Autowired与@Qualifier
2.5.3 @Resource
2.6 全注解式开发
3 JdbcTemplate
3.1 环境准备
3.2 新增
3.3 修改
3.4 删除
3.5 查询一个对象
3.6 查询多个对象
3.7 查询一个值
3.8 批量添加
3.9 使用回调函数
3.10 使用德鲁伊连接池
4 GoF之代理模式
4.1 代理模式的理解
4.2 静态代理
4.3 动态代理
4.3.1 JDK动态代理
4.3.2 CGLIB动态代理
5 面向切面编程AOP
5.1 AOP介绍
5.2 AOP的七大术语
5.3 切点表达式
5.4 使用Spring的AOP
5.4.1 准备工作
5.4.2 基于AspectJ的AOP注解式开发
5.4.3 基于XML配置方式的AOP(了解)
5.5 AOP实际开发案例:事务处理
5.6 AOP的实际案例:安全日志
下次一定写
注解的存在是为了简化XML的配置。Spring6倡导全注解式开发
我们先回顾一下:注解怎么定义、使用
注解的定义:
package com.itzw.annotationn;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(value = {ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
String value();
}
注解的使用:
package com.itzw.annotationn;
@Component("hello")
public class Hello {
}
用法简单,如果属性名为value,那么value可以省略不写
负责声明Bean的注解,常见的包括四个:
我们通过源码发现,这后三个注解都是第一个注解的别名,也就是说,这四个注解其实功能是一样的,用哪个都可以。但是为了强调程序的可读性,建议:
它们都只有一个value属性,value属性用来指定bean的id,也就是bean的名字
如何使用以上注解呢?
第一步:加入aop依赖
当我们加入spring-context依赖之后,会关联aop依赖
第二步:在配置文件中添加context命名空间
第三步:在配置文件中指定扫描的包
第四步:在Bean类上使用注解
package com.itzw.spring6.dao;
import org.springframework.stereotype.Component;
@Component("user")
public class User {
}
测试程序:
@Test
public void testAnnotation(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
User user = applicationContext.getBean("user", User.class);
System.out.println(user);
}
结果:
值得注意的是:该注解是可以不赋值的,默认值为该类名的首字母小写
package com.itzw.spring6.dao;
import org.springframework.stereotype.Component;
@Component
public class User {
}
去掉赋的值测试依然没问题
我们将Component注解换成其它三个注解试一下。经测试都是可以使用的
如果有多个包怎么办:
我们先来测试第一种方式:
经测试没问题
我们再来第二种方式:
这两个包都有一个共同的父包,所以我们直接把包名写为父包:
但这样会降低运行效率
因为我们有四个注解都可以用来帮助我们实例化Bean,但假如我只想实例化某一个注解下的Bean类呢?这种应用很少。
我们先实例化全部:
package com.itzw.spring6.dao;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
@Component
public class A {
public A(){
System.out.println("A的无参构造执行了");
}
}
@Controller
class B {
public B(){
System.out.println("B的无参构造执行了");
}
}
@Service
class C {
public C(){
System.out.println("C的无参构造执行了");
}
}
@Repository
class D {
public D(){
System.out.println("D的无参构造执行了");
}
}
@Test
public void testSelect(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-select.xml");
}
假如我只想实例化注解Controller下的Bean类:
也可以将use-default-filters设置为true(不写就是true),并且采用exclude-filter方式排出哪些注解标注的Bean不参与实例化:
因为use-default-filters默认就是true,所以我们可以不写,这里我们排除Repository
前面只是讲到实例化Bean,但是我们是需要给参数传值的,也就是注入
给Bean属性赋值需要用到这些注解:
当属性是简单类型时可以使用@Value进行注入
package com.itzw.spring6.dao;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository;
@Repository
public class Student {
@Value("张麻子")
private String name;
@Value("34")
private int age;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
测试结果:
没什么毛病
通过以上方法注入我们发现不需要提供setter方法就可以注入了,以前白学了。所以以后我们 直接在属性上进行注入就可以
但其实我们依然可以提供setter方法然后在setter方法上使用@Value进行注入,但这是何必呢,硬给自己找事干?所以这里我们直接不演示这种方法了。同样的,它也可以在构造方法参数前进行注入,但是还是那句话,我们直接在属性上注入就好,简单又方便啊兄弟们。
@Autowired注解可以用来注入非简单类型。被翻译为:自动连线的,或者自动装配。
单独使用@Autowired,默认根据类型装配。就是我们之前学的根据类型自动装配
查看它的源码:
package org.springframework.beans.factory.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
我们可以得出:该注解可以标注在:构造方法、方法、形参、属性、注解上;该注解有一个required属性,默认值是true,表示在注入的时候要求被注入的Bean必须是存在的,如果不存在则报错。如果required的值设为false,表示注入的Bean存在或者不存在都没关系,存在就注入,不存在也不报错。
我们先在属性上使用@Autowired注解:
package com.itzw.spring6.dao;
public interface OrderDao {
void insert();
}
package com.itzw.spring6.dao.impl;
import com.itzw.spring6.dao.OrderDao;
import org.springframework.stereotype.Repository;
@Repository
public class OrderDaoImpl implements OrderDao {
@Override
public void insert() {
System.out.println("正在插入信息。。");
}
}
package com.itzw.spring6.service;
import com.itzw.spring6.dao.OrderDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@Autowired
private OrderDao orderDao;
public void add(){
orderDao.insert();
}
}
测试没有问题.
我们没有提供setter方法也没有提供构造方法,只是在要注入的属性上写了一个注解就可以。当然我们也可以在setter方法上使用注解,在构造方法上使用,在构造方法的参数上使用。甚至当我们创建了构造方法后都不用写这个注解,但只限于一个构造方法。
其实这么多方式进行注入,还是使用最简单的在属性上写注解就好了。
之前说了这个注解默认是根据类型(byType)注入的,但如果OrderDao接口还有一个类呢?
package com.itzw.spring6.dao.impl;
import com.itzw.spring6.dao.OrderDao;
import org.springframework.stereotype.Repository;
@Repository
public class OrderDaoImplByOracle implements OrderDao {
@Override
public void insert() {
System.out.println("oracle数据库正在连接。。");
}
}
我们再进行测试,这时会出错
那怎么解决呢?使用byName,根据name进行装配
@Autowired注解和@Qualifier注解联合起来才可以根据名称进行装配,在@Qualifier注解中指定Bean名称。
总结:
@Resource注解属于JDK扩展包,所以不在JDK当中,需要额外引入已下依赖:(如果是JDK8的话不需要引入依赖,高于JDK11或者低于JDK8需要引入以下依赖)
jakarta.annotation
jakarta.annotation-api
2.1.1
一定要注意:如果你用Spring6,要知道Spring6不再支持JavaEE,它支持的是JakartaEE9。(Oracle把JavaEE贡献给Apache了,Apache把JavaEE的名字改成JakartaEE了,大家之前所接触的所有的 javax.* 包名统一修改为 jakarta.*包名了。)
我这里使用的就是spring6,所以用上面的依赖就可以,如果是spring6以下的用别的依赖
我们直接修改之前的代码,给类OrderDaoImplByOracle的名字命名为“xyz”
在service文件中使用@Resource
测试没有问题
我们再修改dao类的名字,使用默认名字,@Resource注解也去掉名字,使用默认:这样会报错
我们再修改dao类的名字,使用service文件中属性的名字命名,也就是orderDao:
这样可以正常运行,通过测试得知,当@Resource注解使用时没有指定name的时候,还是根据name进行查找,这个name是属性名。如果还找不到会根据类型(byType)查找,如果还找不到就报错
所以前面我们用配置文件用了半天结果我们不需要用了,白学公主就是我。
我们需要写一个配置类代替配置文件:
package com.itzw.spring6.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan({"com.itzw.spring6.dao","com.itzw.spring6.service"})
public class Spring6Configuration {
}
其中@ComponentScan里面填写需要使用到的包名
编写测试程序,不再new ClassPathXmlApplicationContext()对象了。
@Test
public void testNoXML(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Configuration.class);
OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
orderService.add();
}
JdbcTemplate是spring提供的一个JDBC模板类,是对JDBC的封装,简化JDBC代码,当然也可以不用,可以让spring继承其它的ORM框架,如:mybatis、hibernate等。
数据库准备:
idea准备,添加依赖:
repository.spring.milestone
Spring Milestone Repository
https://repo.spring.io/milestone
org.springframework
spring-context
6.0.0-M2
junit
junit
4.13.2
test
mysql
mysql-connector-java
8.0.30
org.springframework
spring-jdbc
6.0.0-M2
表t_user对应的User实体类:
package com.itzw.spring6.bean;
public class User {
private Integer id;
private String realName;
private Integer age;
public User() {
}
public User(Integer id, String realName, Integer age) {
this.id = id;
this.realName = realName;
this.age = age;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getRealName() {
return realName;
}
public void setRealName(String realName) {
this.realName = realName;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", realName='" + realName + '\'' +
", age=" + age +
'}';
}
}
编写spring配置文件:
JdbcTemplate是spring提供好的类,完整名为:org.springframework.jdbc.core.JdbcTemplate,怎么使用这个类呢?直接new对象就可以,直接将这个类配置到spring配置文件中,纳入bean管理。
我们查看这个类的源码发现它有个属性为DataSource,这个属性是数据源。连接数据库需要Connection对象,而生成Connection对象是数据源负责的,所以我们需要给JdbcTemplate设置数据源属性。并且所有数据源都是要实现javax.sql.DataSource接口的,这个数据源可以自己写一个,当然也可以用写好的,比如:阿里巴巴的德鲁伊连接池,c3p0,dbcp等,我们这里先自己手写一个数据源。
MyDataSource类:重点写getConnection方法
package com.itzw.spring6.jdbc;
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
public class MyDataSource implements DataSource {
private String driver;
private String url;
private String username;
private String password;
public String getDriver() {
return driver;
}
public void setDriver(String driver) {
this.driver = driver;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
// 重点写怎么获取Connection对象就行。其他方法不用管。
@Override
public Connection getConnection() throws SQLException {
try {
Class.forName(driver);
Connection conn = DriverManager.getConnection(url, username, password);
return conn;
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
@Override
public T unwrap(Class iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class> iface) throws SQLException {
return false;
}
}
在spring配置文件注入数据,并把数据源注入给JdbcTemplate:
这样环境就准备完了,下面开始增删改查
@Test
public void testInsert(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-jdbc.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "insert into t_user(real_name,age) value(?,?)";
int count = jdbcTemplate.update(sql, "张三", 12);
System.out.println(count);
}
注意:update执行的两个参数:第一个参数是需要执行的SQL语句;第二个参数是可边长参数,可以 是0个也可以是多个,一般是SQL语句有几个问号对应几个参数。
只要是对数据库表有修改的SQL语句如增删改都是使用update方法。
@Test
public void testUpdate(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-jdbc.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "update t_user set real_name = ?,age = ? where id = ?";
int count = jdbcTemplate.update(sql, "lisi", 22,4);
System.out.println(count);
}
@Test
public void testDelete(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-jdbc.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "delete from t_user where id = ?";
int count = jdbcTemplate.update(sql, 4);
System.out.println(count);
}
@Test
public void testSelect(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-jdbc.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "select * from t_user where id = ?";
User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), 1);
System.out.println(user);
}
queryForObject方法三个参数:
@Test
public void testSelectAll(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-jdbc.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "select * from t_user";
List users = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
System.out.println(users);
}
查询多个把queryForObject方法换成query方法。
查询表的记录条数
@Test
public void testSelectNumber(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-jdbc.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "select count(1) from t_user";
Integer count = jdbcTemplate.queryForObject(sql, int.class);
System.out.println(count);
}
此时queryForObject方法参数需要标明返回的数据类型
@Test
public void testBatchInsert(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-jdbc.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "insert into t_user(real_name,age) value(?,?)";
Object[] obj1 = {"张三1",12};
Object[] obj2 = {"张三2",13};
Object[] obj3 = {"张三3",14};
List
还有批量修改批量删除,类似
非常的愚蠢,不用
之前的数据源是我们自己写的,我们也可以使用别人写的:
第一步:引入德鲁伊连接池依赖
com.alibaba
druid
1.1.8
第二步:将德鲁伊中的数据源配置到spring配置文件中。和配置我们自己写的一样。
我们测试,查询表的记录条数:
比如演员需要替身,这里的替身可以代替演员完成表演,替身就是代理类,演员就是目标类,它们都是为了完成一个事情,所以继承的是同一个接口。在这里演员也就是代理类可以做到保护演员的作用;可以完成演员完成不了的动作实现了增强作用;当演员有事的时候,可以实现代替作用。
总的来说代理模式的作用是为其它对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用一个对象,可以通过“代理对象”实现间接引用,并且它可以添加额外的服务。
代理模式中的角色:
代理模式在代码的实现上有两种形式:
有以下接口和实现类:
package com.itzw.proxy.service;
public interface OrderService {
//生成订单
void generate();
//查看订单
void detail();
//修改订单
void modify();
}
package com.itzw.proxy.service;
public class OrderServiceImpl implements OrderService{
@Override
public void generate() {
try {
Thread.sleep(1234);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单正在生成。。");
}
@Override
public void detail() {
try {
Thread.sleep(234);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("正在查看订单。。");
}
@Override
public void modify() {
try {
Thread.sleep(123);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("正在修改订单。。");
}
}
我们模拟用户使用客户端测试:
package com.itzw.proxy.client;
import com.itzw.proxy.service.OrderService;
import com.itzw.proxy.service.OrderServiceImpl;
public class Test {
public static void main(String[] args) {
OrderService orderService = new OrderServiceImpl();
orderService.generate();
orderService.detail();
orderService.modify();
}
}
假如现在用户有新需求,程序运行太慢了。这时候我们想找出哪个功能用时最长,该怎么找出这个功能
第一种方式:直接修改源代码
这样最简单,但是违背了编程的OCP开闭原则,我们不想修改源代码
第二种方案:编写一个子类继承OrderServiceImpl,在子类中重写每个方法,代码如下:
package com.itzw.proxy.service;
public class OrderServiceImplSub extends OrderServiceImpl{
@Override
public void generate() {
long start = System.currentTimeMillis();
super.generate();
long end = System.currentTimeMillis();
System.out.println("用时:" + (end - start) + "秒");
}
@Override
public void detail() {
long start = System.currentTimeMillis();
super.detail();
long end = System.currentTimeMillis();
System.out.println("用时:" + (end - start) + "秒");
}
@Override
public void modify() {
long start = System.currentTimeMillis();
super.modify();
long end = System.currentTimeMillis();
System.out.println("用时:" + (end - start) + "秒");
}
}
这种方式确实可以解决这个问题,但是也有缺点:
假设系统有100个这样的业务,需要提供100个子类,会产生类爆炸;由于是继承的方式,导致代码之间的耦合度比较高。
第三种方式:使用代理模式
为OrderService接口提供一个代理类:
package com.itzw.proxy.service;
public class OrderServiceProxy implements OrderService{
private OrderService orderService;
public OrderServiceProxy(OrderService orderService) {
this.orderService = orderService;
}
@Override
public void generate() {
long start = System.currentTimeMillis();
orderService.generate();
long end = System.currentTimeMillis();
System.out.println("用时:" + (end - start) + "秒");
}
@Override
public void detail() {
long start = System.currentTimeMillis();
orderService.detail();
long end = System.currentTimeMillis();
System.out.println("用时:" + (end - start) + "秒");
}
@Override
public void modify() {
long start = System.currentTimeMillis();
orderService.modify();
long end = System.currentTimeMillis();
System.out.println("用时:" + (end - start) + "秒");
}
}
OrderService target = new OrderServiceImpl();
OrderService orderService = new OrderServiceProxy(target);
orderService.generate();
orderService.detail();
orderService.modify();
这样使用的是关联关系而不是继承关系,关联度低了很多。但是我们发现这样依然会产生类爆炸,一个接口对应一个代理类显然不合理。动态代理可以解决这个问题,在动态代理中可以在内存中动态的为我们生成代理类的字节码,代理类不需要我们写了
在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。
在内存中动态生成类的技术常见的包括:
我们首先还是写一个接口和实现类,只不过这次不需要再写代理类了,这个可以动态生成,我们直接写客户端程序即可:
package com.itzw.proxy.client;
import com.itzw.proxy.service.OrderService;
import com.itzw.proxy.service.OrderServiceImpl;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) {
//第一步:创建目标对象
OrderService target = new OrderServiceImpl();
//第二步:创建代理对象
OrderService orderServiceProxy = Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),调用处理器对象);
//第三步:调用代理对象的代理方法
orderServiceProxy.generate();
orderServiceProxy.detail();
orderServiceProxy.modify();
}
}
以上三步就能完成动态代理,尤其注意第二步:
这行代码做了两件事:
Proxy类全名是:java.lang.reflect.Proxy。这是JDK提供的一个类
其中newProxyInstance()方法中有三个参数:
所以接下来要写java.lang.reflect.InvocationHandler接口的实现类,并且实现 接口中的方法,如下
package com.itzw.proxy.service;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class TimerInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
}
我们发现这个接口中有个方法invoke,这个invoke方法上有三个参数:
我们将来是要调用“目标方法”的,但要调用目标方法需要“目标对象”的存在,我们可以给这个接口提供一个构造方法,可以通过这个构造方法传过来“目标对象”,如下:
//目标对象
Object target;
public TimerInvocationHandler(Object target) {
this.target = target;
}
有了目标对象我们就可以在invoke()方法中调用目标方法了。代码如下:
package com.itzw.proxy.service;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class TimerInvocationHandler implements InvocationHandler {
//目标对象
Object target;
public TimerInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//增强部分代码
long start = System.currentTimeMillis();
//调用目标对象的目标方法
//还记得调用方法需要四要素:对象、方法名、参数、返回值
Object retVal = method.invoke(target, args);
//增强部分代码
long end = System.currentTimeMillis();
System.out.println("用时" + (end - start) + "毫秒");
return retVal;
}
}
目前为止调用处理器完成了,接下来完善client程序:
package com.itzw.proxy.client;
import com.itzw.proxy.service.OrderService;
import com.itzw.proxy.service.OrderServiceImpl;
import com.itzw.proxy.service.TimerInvocationHandler;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) {
//第一步:创建目标对象
OrderService target = new OrderServiceImpl();
//第二步:创建代理对象
OrderService orderServiceProxy = (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),new TimerInvocationHandler(target));
//第三步:调用代理对象的代理方法
orderServiceProxy.generate();
orderServiceProxy.detail();
orderServiceProxy.modify();
}
}
目前为止,动态代理就完成了,但是有个小问题是,我们没有看见invoke方法被调用啊,注意当我们调用代理对象的代理方法时,invoke方法就会被调用,就是上面的第三步。
了解一下
AOP(Aspect Oriented Programming):面向切面编程(AOP是一种编程技术)
AOP底层使用的就是动态代理来实现的。
Spring的AOP使用的动态代理是:JDK动态代理+CGLIB动态代理技术。spring在这两种 动态代理中灵活切换,如果是代理接口,会默认使用JDK动态代理,如果要代理某个类,这个类没有实现接口,就会切换使用CGLIB。当然也可以配置让Spring只使用CGLIB。
一般一个系统都会有一些系统服务,例如:日志、事务管理、安全等,这些系统服务被称为:交叉业务,这些交叉业务几乎是通用的
我们在上面使用的JDK动态代理就是面向AOP编程的
连接点 Joinpoint
切点Pointcut
通知Advice
切面 Aspect
织入 Weaving
代理对象Proxy
目标对象Target
切点表达式的格式:
execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形式参数列表) [异常])
访问控制权限修饰符:
返回值类型:
全限定类名:
方法名:
形式参数列表:
异常:
Spring对AOP的实现包括一下3种方式:
实际开发中,都是Spring+AspectJ来实现AOP,所以我们重点学习第一种和第二种方式。
使用Spring+AspectJ的AOP需要引入的依赖如下:
org.springframework
spring-context
6.0.0-M2
org.springframework
spring-aspects
6.0.0-M2
注意我们还需要aop依赖,因为context依赖包含了aop依赖,所以不需要单独引用了。
Spring配置文件中添加context命名空间和aop命名空间:
实现步骤:
第一步:定义目标类和目标方法
package com.itzw.spring6.service;
public class OrderService {
public void generate(){
//目标方法
System.out.println("订单正在生成。。");
}
}
第二步:定义切面类,注意切面类上面要加注解@Aspect表示它是一个切面(通知+切点)
第三步:目标类和切面类都纳入spring bean管理
第四步:在spring配置文件中加入组件扫描
第五步:在切面类中添加通知(增强代码)
@Aspect
@Component
public class LogAspect {
public void advice(){
System.out.println("我是增强代码,我是一个通知");
}
}
第六步:在通知上添加切点表达式(指定对哪个类的哪个方法进行增强也就是通知)
@Aspect
@Component
public class LogAspect {
//切点表达式
@Before("execution(* com.itzw.spring6.service.OrderService.*(..))")
//这就是需要增强的代码,也就是通知
public void advice(){
System.out.println("我是增强代码,我是一个通知");
}
}
注意:这里的Before注解表示前置通知,也就是在指定方法前进行通知(增强)
第七步:在spring配置文件中启用自动代理
测试程序:
@Test
public void testBeforeAdvice(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-aop.xml");
OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
orderService.generate();
}
通知类型包括:
我们依次测试这些通知:
package com.itzw.spring6.service;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LogAspect {
/* //切点表达式
@Before("execution(* com.itzw.spring6.service.OrderService.*(..))")
//这就是需要增强的代码,也就是通知
public void beforeAdvice(){
System.out.println("我是增强代码,我是一个通知");
}*/
@Before("execution(* com.itzw.spring6.service.OrderService.*(..))")
public void beforeAdvice(){
System.out.println("前置通知");
}
@AfterReturning("execution(* com.itzw.spring6.service.OrderService.*(..))")
public void AfterReturningAdvice(){
System.out.println("后置通知");
}
@Around("execution(* com.itzw.spring6.service.OrderService.*(..))")
public void AroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕通知开始");
//执行目标方法
joinPoint.proceed();
System.out.println("环绕通知结束");
}
@AfterThrowing("execution(* com.itzw.spring6.service.OrderService.*(..))")
public void AfterThrowingAdvice(){
System.out.println("异常通知");
}
@After("execution(* com.itzw.spring6.service.OrderService.*(..))")
public void AfterAdvice(){
System.out.println("最终通知");
}
}
查看结果:
这就是这些通知的顺序,但是没有异常通知,因为没有异常,我们模拟一手异常:
package com.itzw.spring6.service;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
public void generate(){
//目标方法
System.out.println("订单正在生成。。");
if (true){
throw new RuntimeException("模拟异常发生");
}
}
}
结果:
通过测试得知,当发生异常之后,最终通知也会执行,因为最终通知@After会出现在finally语句块中。出现异常之后,后置通知和环绕通知的结束部分不会执行。
切面的顺序
我们知道,一个业务不可能只有一个切面,也就是说还存在例如安全控制、时间计算等切面,那它们的执行顺序怎样控制呢?可以使用Order注解,值越小优先级越高
我们再创建一个切面:
结果:
优化使用切点表达式:
我们发现之前的代码中的切点表达式都是重复的,没有得到复用。并且如果要修改就要修改多处:
我们可以将切点表达式单独的定义出来,在需要的位置引入即可。如下:
package com.itzw.spring6.service;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Aspect
@Component
@Order(2)
public class LogAspect {
/* //切点表达式
@Before("execution(* com.itzw.spring6.service.OrderService.*(..))")
//这就是需要增强的代码,也就是通知
public void beforeAdvice(){
System.out.println("我是增强代码,我是一个通知");
}*/
@Pointcut("execution(* com.itzw.spring6.service.OrderService.*(..))")
public void pointcut(){}
@Before("pointcut()")
public void beforeAdvice(){
System.out.println("Log'前置通知");
}
@AfterReturning("pointcut()")
public void AfterReturningAdvice(){
System.out.println("后置通知");
}
@Around("pointcut()")
public void AroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕通知开始");
//执行目标方法
joinPoint.proceed();
System.out.println("环绕通知结束");
}
@AfterThrowing("pointcut()")
public void AfterThrowingAdvice(){
System.out.println("异常通知");
}
@After("pointcut()")
public void AfterAdvice(){
System.out.println("最终通知");
}
}
全注解式开发AOP
就是编写一个类,使用注解来代理之前的spring配置文件,因为我们已经使用注解了,而且大部分都用注解实现了,就一点点配置用到spring配置文件。这个我们在之前接触到过。如下:
package com.itzw.spring6.service;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan("com.itzw.spring6.service")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class Spring6Configuration {
}
测试程序也需要修改一下:
@Test
public void testNoXml(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Configuration.class);
OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
orderService.generate();
}
我们使用比较多的还是注解式开发,所以这个了解即可:
一个项目肯定有很多业务,而这些业务极可能有多条DML语句,我们就需要使用事务处理避免出现错误,如果我们在每一个业务中都添加这些业务,显然很愚蠢,既然我们学了AOP,就使用AOP的方式进行事务处理。
有两个业务如下:
package com.itzw.spring6.service;
import org.springframework.stereotype.Service;
@Service
public class AccountService {
public void tranfer(){
System.out.println("正在转账。。。");
}
public void withdraw(){
System.out.println("正在取款。。。");
}
}
package com.itzw.spring6.service;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
public void generate(){
System.out.println("正在生成订单。。");
}
public void cancel(){
System.out.println("正在取消订单。。");
}
}
以上两个业务都纳入spring bean的管理,下面我们使用AOP进行事务处理:
package com.itzw.spring6.service;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class TransactionAspect {
@Around("execution(* com.itzw.spring6.service..*(..))")
public void arountAdvice(ProceedingJoinPoint joinPoint){
try {
System.out.println("开启事务");
//执行目标程序
joinPoint.proceed();
System.out.println("提交事务");
} catch (Throwable e) {
System.out.println("回滚事务");
}
}
}
这个事务只写了一次却能应用于多个业务
这里我们不配置spring配置文件了:
package com.itzw.spring6.service;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan("com.itzw.spring6.service")
@EnableAspectJAutoProxy
public class Spring6Configuration {
}
测试:
package com.itzw.spring6.test;
import com.itzw.spring6.service.AccountService;
import com.itzw.spring6.service.OrderService;
import com.itzw.spring6.service.Spring6Configuration;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SpringAopTest {
@Test
public void test(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Configuration.class);
OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
orderService.generate();
orderService.cancel();
accountService.tranfer();
accountService.withdraw();
}
}
结果:
我们可以看到所有的业务方法都得到了事务控制。
我们手动添加一个异常,再次测试:
需求是这样的:项目开发结束了,已经上线了。运行正常。客户提出了新的需求:凡事在系统中进行修改操作的,删除操作的,新增操作的,都要把这个人记录下来。因为这几个操作是属于危险行为。例如有业务类和业务方法
先写两个业务:
package com.itzw.spring6.service2;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void getUser(){
System.out.println("获取用户信息");
}
public void saveUser(){
System.out.println("保存用户信息");
}
public void deleteUser(){
System.out.println("删除用户信息");
}
public void modifyUser(){
System.out.println("修改用户信息");
}
}
package com.itzw.spring6.service2;
import org.springframework.stereotype.Service;
@Service
public class ProductService {
public void getProduct(){
System.out.println("获取商品信息");
}
public void saveProduct(){
System.out.println("保存商品信息");
}
public void deleteProduct(){
System.out.println("删除商品信息");
}
public void modifyProduct(){
System.out.println("修改商品信息");
}
}
日志切面:
package com.itzw.spring6.service2;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
@Component
@Aspect
public class LogAspect {
@Pointcut("execution(* com.itzw.spring6.service2..save*(..))")
public void savePointcut(){}
@Pointcut("execution(* com.itzw.spring6.service2..delete*(..))")
public void deletePointcut(){}
@Pointcut("execution(* com.itzw.spring6.service2..modify*(..))")
public void modifyPointcut(){}
@Before("savePointcut() || deletePointcut() || modifyPointcut()")
public void beaforeAdvice(JoinPoint joinPoint){
//系统时间
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String nowTime = sdf.format(new Date());
System.out.println(nowTime+"张三:"+joinPoint.getSignature().getDeclaringTypeName()+"."+joinPoint.getSignature().getName());
}
}
package com.itzw.spring6.service2;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.itzw.spring6.service2")
public class Spring6Configuration {
}
测试:
@Test
public void test2(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Configuration.class);
UserService userService = applicationContext.getBean("userService", UserService.class);
ProductService productService = applicationContext.getBean("productService", ProductService.class);
userService.getUser();
userService.saveUser();
userService.deleteUser();
userService.modifyUser();
productService.getProduct();
productService.saveProduct();
productService.deleteProduct();
productService.modifyProduct();
}
结果: