
耦合:程序间的依赖关系
耦合包括:
解耦:
? 降低程序间的依赖关系
开发解耦:
解耦思路:
new关键字Bean:可重用组件
JavaBean:用Java语言编写的可重用组件(≠实体类,>实体类)
使用配置文件配置需要解耦的类
配置内容:唯一标识=全类名
通过读取配置文件中的配置内容,反射创建对象,存入Bean容器中
bean.properties
userService=com.spring.jdbc.service.UserServicecom.spring.jdbc.service.UserService
public class UserService {
    public void useService(){
        System.out.println("hello");
    }
}创建Bean工厂,加载bean.properties中的配置,将需要解耦的对象加载进Bean容器,从而实现了对象的单例模式
com.spring.jdbc.factory.BeanFactory
public class BeanFactory {
    /**
     * 定义一个properties对象
     */
    private static Properties properties;
    /**
     * 定义一个Map,作为存储Bean的容器
     */
    private static Map<String, Object> beans;
    /**
     * 使用静态代码块为Properties对象赋值并初始化Bean容器
     */
    static {
        try {
            /**
             * 1.实例化配置对象
             */
            properties = new Properties();
            //加载ClassPath下的配置文件,获取properties的流对象
            InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            //将配置文件的内容进行装载
            properties.load(in);
            /**
             * 2.实例化Bean的容器
             */
            beans = new HashMap<String, Object>();
            /**
             * 3.加载配置文件并将反射的对象存入容器中
             */
            //获取key的枚举
            Enumeration<Object> keys = properties.keys();
            while (keys.hasMoreElements()) {
                //取出每个key
                String key = keys.nextElement().toString();
                //根据key获取value
                String beanPath = properties.getProperty(key);
                //反射创建对象
                Object value = Class.forName(beanPath).newInstance();
                //把key和value存入容器中
                beans.put(key, value);
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new ExceptionInInitializerError("初始化properties失败");
        }
    }
    /**
     * 根据Bean名称获取Bean对象
     *
     * @param beanName
     * @return
     */
    public static Object getBean(String beanName) {
        return beans.get(beanName);
    }
}使用
public class Main {
    public static void main(String[] args) {
        UserService userService = (UserService) BeanFactory.getBean("userService");
        userService.useService();
    }
}将创建类权力交给了工厂(框架),放弃了自己对类的创建控制
削减了程序的耦合
导入Spring框架依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.4.RELEASE</version>
</dependency>创建Spring的Bean配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>将对象的创建交给Spring去管理
id:Bean的名称,class:反射对象的全类名
<bean id="userService" class="com.spring.jdbc.service.UserService"></bean>使用
public class Jdbc {
    /**
     * 获取IOC的核心容器,并根据id获取对象
     * @param args
     */
    public static void main(String[] args) {
        //1.获取容器对象
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.1 根据id获取Object类对象
        UserService userService = (UserService)ac.getBean("userService");
        //2.2 根据id和类对象直接获取对象
        //UserService userService1 = ac.getBean("userService",UserService.class);
        userService.useService();
    }
}用于根据参数构建bean工厂
可以加载类路径下的配置文件
加载磁盘任意路径下的配置文件(必须有访问权限)
用于读取注解创建容器
单例对象适用
在构建核心容器时,创建对象的策略是立即加载
只要一读取完配置文件,就立刻创建对象
多例对象适用
在构造核心容器是,采用延迟加载的方式加载
只有在通过容器获取对象的时候才创建对象
使用默认构造函数创建
在Spring的配置文件中使用bean标签,除了id和class没有其他标签时使用
如果该类没有默认构造函数那么该对象无法创建
<bean id="userService" class="com.spring.jdbc.service.UserService"></bean>使用某个类中的方法返回值创建对象并存入容器
com.spring.jdbc.factory.InstanceFactory
public class InstanceFactory {
    public UserService getUserService(){
        return new UserService();
    }
}
bean.xml
<!--将工厂存入容器-->
<bean id="instanceFactory" class="com.spring.jdbc.factory.InstanceFactory"></bean>
<!--从工厂中的方法中获取bean-->
<bean id="userService" factory-bean="instanceFactory" factory-method="getUserService"></bean>使用某个类中的静态方法创建对象并存入容器
com.spring.jdbc.factory.StaticFactory
public class StaticFactory {
    public static UserService getUserService(){
        return new UserService();
    }
}
bean.xml
<bean id="userService" class="com.spring.jdbc.factory.StaticFactory" factory-method="getUserService"></bean>改变Bean标签的scope属性
可以在Bean标签中加入
init-method、destory-method指创建和销毁对象时运行的函数要调用
destory-method需要调用容器close方法,不然容器会直接销毁,无法查看效果
依赖注入就是依赖关系的维护
能注入的类型:
- 基本类型和String
 - 其他Bean类型(在配置文件中或者注解标识的)
 - 复杂类型/集合类型
 注入方式:
- 构造函数提供
 - 使用set方法提供
 - 使用注解提供
 
使用constructor-arg标签给构造函数的参数赋值
缺点:必须在xml中配置所有的构造函数才能注入,不常用
com.spring.jdbc.service.UserService
public class UserService {
    /**
     * 如果是经常变化的数据不适用于注入
     */
    private String name;
    private Integer age;
    private Date birthday;
    public UserService(String name, Integer age, Date birthday) {
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }
    public void useService(){
        System.out.println(name+" "+age+" "+birthday);
    }
}
bean.xml
<!--构造函数的注入,使用constructor-arg标签指定构造函数-->
<bean id="userService" class="com.spring.jdbc.service.UserService">
    <constructor-arg name="name" value="test"></constructor-arg>
    <constructor-arg name="age" value="18"></constructor-arg>
    <constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
<!--注入一个当前时间的日期对象-->
<bean id="now" class="java.util.Date"></bean>
<!--注入一个自定义的日期对象-->
<!--<bean id="now" class="java.util.Date">-->
<!--    <constructor-arg name="year" value="100"></constructor-arg>-->
<!--    <constructor-arg name="month" value="1"></constructor-arg>-->
<!--    <constructor-arg name="date" value="1"></constructor-arg>-->
<!--</bean>-->
type:指定要注入的数据类型,该数据类型是构造函数中某个或者某些参数的类型
再使用value将指定的值注入给指定的数据类型
index:指定赋值的索引位置,起始为0
name:指定赋值的参数名字
value:将基本数据类型和String类型赋给上面三种方式指定的参数的值
ref:指定其他的Bean类型(在SpringIOC容器中出现的Bean对象)给上面三种方式指定的参数赋值
使用property配置需要赋值的参数
优势:更加灵活选择赋值,更常用
com.spring.jdbc.service.UserService
public class UserService {
    private String name;
    private Integer age;
    private Date birthday;
    public void setName(String name) {
        this.name = name;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
    public void useService(){
        System.out.println(name+" "+age+" "+birthday);
    }
}
bean.xml
<bean id="userService" class="com.spring.jdbc.service.UserService">
    <property name="name" value="test"></property>
    <property name="age" value="18"></property>
    <property name="birthday" ref="now"></property>
</bean>
<bean id="now" class="java.util.Date">
    <constructor-arg name="year" value="100"></constructor-arg>
    <constructor-arg name="month" value="1"></constructor-arg>
    <constructor-arg name="date" value="1"></constructor-arg>
</bean>
com.spring.jdbc.service.UserService
public class UserService {
    private String[] myStrs;
    private List<String> myList;
    private Set<String> mySet;
    private Map<String,Object> myMap;
    private Properties myPro;
    public void setMyStrs(String[] myStrs) {
        this.myStrs = myStrs;
    }
    public void setMyList(List<String> myList) {
        this.myList = myList;
    }
    public void setMySet(Set<String> mySet) {
        this.mySet = mySet;
    }
    public void setMyMap(Map<String, Object> myMap) {
        this.myMap = myMap;
    }
    public void setMyPro(Properties myPro) {
        this.myPro = myPro;
    }
    @Override
    public String toString() {
        return "UserService{" +
                "myStrs=" + Arrays.toString(myStrs) +
                ", myList=" + myList +
                ", mySet=" + mySet +
                ", myMap=" + myMap +
                ", properties=" + myPro +
                '}';
    }
}
Bean.xml
<bean id="userService" class="com.spring.jdbc.service.UserService">
    <property name="myStrs">
        <array>
            <value>A</value>
            <value>B</value>
            <value>C</value>
        </array>
    </property>
    <property name="myList">
        <list>
            <value>A</value>
            <value>B</value>
            <value>C</value>
        </list>
    </property>
    <property name="mySet">
        <set>
            <value>A</value>
            <value>B</value>
            <value>C</value>
        </set>
    </property>
    <property name="myMap">
        <map>
            <!--方式一-->
            <entry key="test1" value="aaa"></entry>
            <!--方式二-->
            <entry key="test2">
                <value>bbb</value>
            </entry>
        </map>
    </property>
    <property name="myPro">
        <props>
            <prop key="test1">aaa</prop>
            <prop key="test2">bbb</prop>
        </props>
    </property>
</bean>
用于给List结构集合注入值的标签:list array set
用于给Map结构集合注入值的标签:map props
结构相同,标签可以互换
注解需要的context命名空间和约束
如果想要去除XML,需要配置配置类
bean.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"
    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">
    <!--告知Spring在创建容器时需要扫描的包,配置所需要的标签不是在beans的约束中,而是在context名称空间和约束中-->
    <context:component-scan base-package="com.spring.jdbc"></context:component-scan>
</beans>
和
标签实现的作用一样 以下注解的作用和属性相同,只是Spring为了让三层架构更加清晰
和
标签作用一样 
在使用注解注入时,Set方法不是必须
以上三个注解都只能注入Bean,基本类型和String的注入如下,但是集合类型的注入只能通过xml
@Value注入配置classpath:表示类路径在
标签中使用 scope属性作用一样标注在类上
在
标签中使用 init-methoddestory-method标签作用一样标注在方法上
AnnotationConfigApplicationContext(如下使用所示)的参数时可以不写,否则配置无效,Spring不会将Bean扫描进去作用:指定Spring在创建容器时要扫描的包
取代XML中的扫描配置
位置:配置类上
属性:
作用:用于导入其他配置类到主配置类中
当使用该注解后,有Imprt注解的类就是父配置类,而导入的都是子配置,子配置可以不需要@Configuration
位置:配置类上
属性:
public class Main {
    public static void main(String[] args) {
        //此时需要用到注解Context实现类去扫描配置类
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfigruation.class);
        UserService userService = (UserService) applicationContext.getBean("userService");
        userService.test();
    }
}
原生Junit无法在执行自己Runner类(main方法)时进行Spring容器的创建
所以必须在测试时进行手动创建容器,不然无法完成Bean的注入使用,比较麻烦
导入依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.2.4.RELEASE</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <!-- 表示开发的时候引入,发布的时候不会加载此包 -->
    <scope>test</scope>
</dependency>使用Junit提供的替换原有Runner(运行器:main方法)的注解,替换成Spring提供的
@RunWith(SpringJUnit4ClassRunner.class)
public class Test(){}告知Spring运行器SpringIOC容器是基于注解还是XML
@ContextConfiguration
属性:
classpath:指定在类路径下.class)@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfigruation.class)
public class test {}
特点:字节码随用随创建,随用随加载
作用:不修改原方法源码给方法增强
分类:
- 基于接口的动态代理
 
- 涉及的类:Proxy
 - 提供者:JDK官方
 - 创建代理对象的要求:被代理类至少实现一个接口,如果没有不能使用
 - 基于子类的动态代理
 
- 涉及的类:Enhancer
 - 提供者:第三方cglib库
 - 创建代理对象的要求:被代理的类不能是最终类
 
使用Proxy类中的
newProxyInstance方法
newProxyInstance
生厂商接口
com.spring.jdbc.proxy.IProducer
public interface IProducer {
    /**
     * 销售
     *
     * @param money
     */
    public void saleProduct(float money);
    /**
     * 售后
     *
     * @param money
     */
    public void afterService(float money);
}生产商实现类
com.spring.jdbc.proxy.Producer
public class Producer implements IProducer{
    /**
     * 销售
     *
     * @param money
     */
    @Override
    public void saleProduct(float money) {
        System.out.println("销售并拿到钱:" + money);
    }
    /**
     * 售后
     *
     * @param money
     */
    @Override
    public void afterService(float money) {
        System.out.println("提供售后服务比拿到钱:" + money);
    }
}代理经销商
com.spring.jdbc.proxy.Client
public class Client {
    public static void main(String[] args) {
        //匿名内部类访问外部成员需要final类型
        final Producer producer = new Producer();
        //创建一个经销商,代理厂家的销售方法
        IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
                producer.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * 作用:执行被代理对象的任何接口方法都会经过该方法
                     * @param proxy 代理对象的引用
                     * @param method 当前执行的方法
                     * @param args 当前执行方法需要的参数
                     * @return 和被代理对象方法有相同的返回值
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        /**
                         * 提供增强代码
                         */
                        //判断当前方法是不是销售
                        if ("saleProduct".equals(method.getName())) {
                            //获取原方法参数
                            Float money = (Float) args[0];
                            //修改传进原方法的参数新方法返回值
                            //参数:1.被代理对象 2.执行方法的参数
                            return method.invoke(producer, money * 0.8f);
                        }
                        return method.invoke(producer, args);
                    }
                });
        proxyProducer.saleProduct(1000);
    }
}使用Ehancer类中的
create方法
create
MethodInterceptor
导入依赖
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.1_3</version>
</dependency>生产厂商
com.spring.jdbc.cglib.Producer
public class Producer{
    /**
     * 销售
     *
     * @param money
     */
    public void saleProduct(float money) {
        System.out.println("销售并拿到钱:" + money);
    }
    /**
     * 售后
     *
     * @param money
     */
    public void afterService(float money) {
        System.out.println("提供售后服务比拿到钱:" + money);
    }
}经销商
com.spring.jdbc.cglib.Client
public class Client {
    public static void main(String[] args) {
        //匿名内部类访问外部成员需要final类型
        final Producer producer = new Producer();
        //创建一个经销商,代理厂家的销售方法
        Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
            /**
             * 作用:执行被代理对象的任何方法都会经过该方法
             * @param o 代理对象的引用
             * @param method 当前执行的方法
             * @param objects 当前执行方法需要的参数
             * 以上三个参数和基于接口的动态代理相同
             * @param methodProxy 当前执行方法的代理对象
             * @return 和被代理对象方法有相同的返回值
             * @throws Throwable
             */
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                /**
                 * 提供增强代码
                 */
                //判断当前方法是不是销售
                if ("saleProduct".equals(method.getName())) {
                    //获取原方法参数
                    Float money = (Float) objects[0];
                    //修改传进原方法的参数新方法返回值
                    //参数:1.被代理对象 2.执行方法的参数
                    return method.invoke(producer, money * 0.8f);
                }
                return method.invoke(producer, objects);
            }
        });
        cglibProducer.saleProduct(1000);
    }
}JoinPoint(连接点):
指被拦截到的点,在Spring中这些点指方法
通俗来说就是原来需要增强的所有方法
PointCut(切入点)
指我们对哪些JointPoint进行拦截定义
即被增强的方法
切入点一定是连接点,连接点不一定是切入点
advice(通知/增强)
拦截到JoinPoint之后需要做的事
Introduction(引介)
特殊的通知,在运行期动态添加一些方法或者Field
Target(目标对象)
代理的目标对象
Weaving(织入)
把增强应用到目标对象来创建新对象的过程
即加入增强的过程
Proxy(代理)
被AOP增强后返回产生的代理类
Aspect(切面)
切入点和通知之间的关系
依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.4.RELEASE</version>
</dependency>
<!--切入点表达式-->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.5</version>
</dependency>账户业务层接口
com.spring.service.IAccountService
public interface IAccountService {
    /**
     * 模拟保存账户
     */
    void saveAccount();
    /**
     * 模拟更新账户
     * @param i
     */
    void updateAccount(int i);
    /**
     * 删除账户
     * @return
     */
    int deleteAccount();
}账户实现类
com.spring.service.impl.AccountServiceImpl
public class AccountServiceImpl implements IAccountService {
    @Override
    public void saveAccount() {
        System.out.println("执行了保存");
    }
    @Override
    public void updateAccount(int i) {
        System.out.println("执行了更新"+i);
    }
    @Override
    public int deleteAccount() {
        System.out.println("执行了删除");
        return 0;
    }
}日志工具类,提供公共方法,作为切面通知
com.spring.utils.Logger
public class Logger {
    /**
     * 用于打印日志,计划让其在切入点方法执行之前执行
     */
    public void printLog(){
        System.out.println("Logger中的printLog方法开始记录日志");
    }
}AOP和IOC的配置文件
bean.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: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/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>AOP的XML配置步骤
将需要增强的Bean交给Spring
<bean id="accountService" class="com.spring.service.impl.AccountServiceImpl"></bean>把通知的Bean也交给Spring
<bean id="logger" class="com.spring.utils.Logger"></bean>使用<aop:config>标签表明开始AOP的配置
在config标签内,使用<aop:aspect>标签表明配置切面
在aspect标签内配置通知的类型,建立通知方法和切入点之间的关系
如<aop:before>前置通知
属性:
method:指定前置通知的方法
pointcut:用于指定切入点表达式,指定业务层需要增强的方法
写法:
关键字:execution(表达式)
表达式:访问修饰符 返回值 包名.包名...类名.方法名(参数列表)
访问修饰符可以省略
返回值:可以使用通配符(*),表示任意返回值
* com.spring.service.impl.AccountServiceImpl.saveAccount()
包名:可以使用通配符,表示任意包,但是有几级包就要有几个*;
也可以使用..表示当前包及其子包
* *..AccountServiceImpl.saveAccount()
类名和方法名:都可以使用*来实现通配
* *..*.*()
参数列表:
intjava.lang.String*表示任意类型,但是必须有参数..表示有无参数均可全通配写法:* *..*.*(..)
实际开发中的写法:
切到业务层实现类下的所有方法
如execution(* com.spring.service.impl.*.*(..))
<?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: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/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--配置IOC-->
    <bean id="accountService" class="com.spring.service.impl.AccountServiceImpl"></bean>
    <!--Spring中基于XML的AOP配置步骤-->
    <!--1.配置通知类-->
    <bean id="logger" class="com.spring.utils.Logger"></bean>
    <!--2.配置AOP-->
    <aop:config>
        <!--3.配置切面-->
        <aop:aspect id="logAdvice" ref="logger">
            <!--4.配置通知的类型且建立通知方法和切入点方法的关联-->
            <aop:before method="printLog" pointcut="execution(public void com.spring.service.impl.AccountServiceImpl.saveAccount())"></aop:before>
        </aop:aspect>
    </aop:config>
</beans>使用
public class test {
    public static void main(String[] args) {
        //获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //获取对象
        IAccountService as = (IAccountService)ac.getBean("accountService");
        //执行方法
        as.saveAccount();
    }
}<aop:before><aop:after-returning><aop:after-throwing><aop:after><?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: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/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--配置IOC-->
    <bean id="accountService" class="com.spring.service.impl.AccountServiceImpl"></bean>
    <!--Spring中基于XML的AOP配置步骤-->
    <!--1.配置通知类-->
    <bean id="logger" class="com.spring.utils.Logger"></bean>
    <!--2.配置AOP-->
    <aop:config>
        <!--3.配置切面-->
        <aop:aspect id="logAdvice" ref="logger">
            <!--4.1 配置前置通知-->
            <aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>
            <!--4.2 配置后置通知-->
            <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>
            <!--4.3 配置异常通知-->
            <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>
            <!--4.4 配置最终通知-->
            <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>
            <!--配置切入点表达式
            id属性指定表达式唯一属性,expression属性指定表达式内容
            1.写在aop:aspect内部只能当前标签使用
            2.写在aop:aspect外部(必须写在引用切面之前),让所有切面可用
            -->
            <aop:pointcut id="pt1" expression="execution(* com.spring.service.impl.*.*(..))"/>
        </aop:aspect>
    </aop:config>
</beans>
实质:他是Spring提供在代码中控制增强方法何时执行的方法
相当于动态代理的接口实现方法
com.spring.utils.Logger
public class Logger {
    /**
     * 环绕通知
     * Spring为我们提供一个接口ProceedingJoinPoint,接口中有一个proceed(),此方法相当于切入点调用方法。
     * 该接口可以作为环绕通知的方法参数,在程序执行时,Spring为我们提供接口的实现类使用
     */
    public Object aroundPrintLog(ProceedingJoinPoint pjp){
        Object returnValue = null;
        try {
            //得到切入点方法执行的参数
            Object[] args = pjp.getArgs();
            //前置通知
            System.out.println("环绕通知中的前置通知");
            //调用切入点方法
            returnValue = pjp.proceed(args);
            //后置通知
            System.out.println("环绕通知中的后置通知");
        } catch (Throwable throwable) {
            //异常通知
            System.out.println("环绕通知中的异常通知");
            throwable.printStackTrace();
        }finally {
            //最终通知
            System.out.println("环绕通知中的最终通知");
            return returnValue;
        }
    }
}beam.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: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/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--配置IOC-->
    <bean id="accountService" class="com.spring.service.impl.AccountServiceImpl"></bean>
    <!--Spring中基于XML的AOP配置步骤-->
    <!--1.配置通知类-->
    <bean id="logger" class="com.spring.utils.Logger"></bean>
    <!--2.配置AOP-->
    <aop:config>
        <!--通用切入表达式-->
        <aop:pointcut id="pt1" expression="execution(* com.spring.service.impl.*.*(..))"/>
        <!--3.配置切面-->
        <aop:aspect id="logAdvice" ref="logger">
            <aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
        </aop:aspect>
    </aop:config>
</beans>Spring注解的后置通知和异常通知会出现顺序问题
环绕通知不会有问题
加入了xmlns:context="http://www.springframework.org/schema/aop"命名空间
bean.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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
    <!--配置Spring创建容器时要扫描的包-->
    <context:component-scan base-package="com.spring"></context:component-scan>
    <!--配置Spring开启注解AOP支持-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
@Component("logger")
@Aspect
public class Logger {
    /**
    * 切入表达式
    */
    @Pointcut("execution(* com.spring.service.impl.*.*(..))")
    private void pt1(){}
    
    /**
     * 前置通知
     */
    @Before("pt1()")
    public void beforePrintLog() {
        System.out.println("前置通知:Logger中的beforePrintLog方法开始记录日志");
    }
    /**
     * 后置通知
     */
    @AfterReturning("pt1()")
    public void afterReturningPrintLog() {
        System.out.println("后置通知:Logger中的afterPrintLog方法开始记录日志");
    }
    /**
     * 异常通知
     */
    @AfterThrowing("pt1()")
    public void afterThrowingPrintLog() {
        System.out.println("异常通知:Logger中的afterThrowingPrintLog方法开始记录日志");
    }
    /**
     * 最终通知
     */
    @After("pt1()")
    public void afterPrintLog() {
        System.out.println("最终通知:Logger中的finallyPrintLog方法开始记录日志");
    }
    /**
     * 环绕通知
     * Spring为我们提供一个接口ProceedingJoinPoint,接口中有一个proceed(),此方法相当于切入点调用方法。
     * 该接口可以作为环绕通知的方法参数,在程序执行时,Spring为我们提供接口的实现类使用
     */
    @Around("pt1()")
    public Object aroundPrintLog(ProceedingJoinPoint pjp){
        Object returnValue = null;
        try {
            //得到切入点方法执行的参数
            Object[] args = pjp.getArgs();
            //前置通知
            System.out.println("环绕通知中的前置通知");
            //调用切入点方法
            returnValue = pjp.proceed(args);
            //后置通知
            System.out.println("环绕通知中的后置通知");
        } catch (Throwable throwable) {
            //异常通知
            System.out.println("环绕通知中的异常通知");
            throwable.printStackTrace();
        }finally {
            //最终通知
            System.out.println("环绕通知中的最终通知");
            return returnValue;
        }
    }
}
在配置类上加上@EnableAspectJAutoProxy,再用注解context加载配置类即可
通知注解上的value和pointcut作用相同,都是用来指定确如表达式,当有pointcut时value会被覆盖
在普通通知中获取切入点方法
JoinPoint是ProceedingJoinPoint父类,没有执行切点的proceed方法
@AfterReturning(value = "execution(* com.spring.service.impl.*.*(..))")
public void afterReturningPrintLog(JoinPoint jp) {
    //获取切入点参数
    Object[] args = jp.getArgs();
}在普通通知里面获取切入点异常
@AfterThrowing(value = "execution(* com.spring.service.impl.*.*(..))",throwing = "exception")
public void afterThrowingPrintLog(Exception exception) {
    System.out.println("异常为:"+exception);
}在普通通知里面获取切入点返回值
@AfterReturning(value = "execution(* com.spring.service.impl.*.*(..))", returning = "result")
public void afterReturningPrintLog(Object result) {
    System.out.println("切入点返回值为:" + result);
}<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.2.4.RELEASE</version>
</dependency>
com.spring.domain.Account
@Data
public class Account implements Serializable {
    private Integer id;
    private String name;
    private Float money;
}com.spring.dao.impl.AccountDaoImpl
public class AccountDaoImpl  extends JdbcDaoSupport implements IAccountDao{
    @Override
    public Account findAccountById(Integer id) {
        List<Account> accounts = getJdbcTemplate().query("select *from account where id=?", new BeanPropertyRowMapper<Account>(Account.class), id);
        return accounts.isEmpty() ? null : accounts.get(0);
    }
    @Override
    public Account findAccountByName(String accountName) {
        List<Account> accounts = getJdbcTemplate().query("select *from account where name=?", new BeanPropertyRowMapper<Account>(Account.class), accountName);
        if (accounts.isEmpty()) {
            return null;
        }
        if (accounts.size() > 1) {
            throw new RuntimeException("结果集不唯一");
        }
        return accounts.get(0);
    }
    @Override
    public void updateAccount(Account account) {
        getJdbcTemplate().update("update account set name=?,money=? where id=?", account.getName(), account.getMoney(), account.getId());
    }
}com.spring.service.impl.AccountServiceImpl
public class AccountServiceImpl implements IAccountService {
    private IAccountDao accountDao;
    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }
    @Override
    public Account findAccountById(Integer id) {
        return accountDao.findAccountById(id);
    }
    @Override
    public void transfer(String sourceName, String targetName, Float money) {
        //1.根据名称查询转出账户
        Account source = accountDao.findAccountByName(sourceName);
        //2.根据名称查询转入账户
        Account target = accountDao.findAccountByName(targetName);
        //3.转出账户减钱
        source.setMoney(source.getMoney()-money);
        //4.转入账户加钱
        target.setMoney(target.getMoney()+money);
        //5.更新转出账户
        accountDao.updateAccount(source);
        //人为发生异常
        int i = 1/0;
        //5.更新转入账户
        accountDao.updateAccount(target);
    }
}XML配置
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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>配置事务管理器,需要指定数据源
<!--数据源配置-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
</bean>
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>配置事务通知器
此时需要导入事务的约束
使用<tx:advice>标签配置事务通知
属性:
配置事务的属性,<tx:attributes>中的<tx:method>标签
属性:
name:需要添加事务的方法名,可以使用通配符*
如:find*表示所有以find开头的方法,或者*表示所有方法。优先范围小的
isolation:指定事务的隔离级别,默认时DEFAULT,表示使用数据库的默认隔离级别
propagation:指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改选;
查询可选SUPPORTS
read-only:用于指定事务是否只读,默认为false(读写);只有查询才设置为true
timeout:用于指定事务的超时时间,默认为-1,表示永不超时,如果指定数值以秒为单位
rollback-for:用于指定一个异常,当产生该异常时事务回滚,其他异常事务不回滚
默认值表示任何异常都回滚
no-rollback-for:用于指定一个异常,当产生该异常时事务不会滚,其他异常事务回滚
默认值表示任何异常都回滚
<!--配置事务的通知,此时需要导入事务的约束,tx和aop的都需要-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <!--配置事务的属性-->
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED" read-only="false"/>
        <tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
    </tx:attributes>
</tx:advice>配置AOP中的通用切入点表达式,指定所有业务层方法
建立事务通知和切入表达式的对应关系
<!--配置AOP-->
<aop:config>
    <!--配置切入点表达式-->
    <aop:pointcut id="pt1" expression="execution(* com.spring.service.impl.*.*(..))"/>
    <!--建立事务通知和切入表达式的对应关系-->
    <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/>
</aop:config>完整配置:
<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--业务层-->
    <bean id="accountService" class="com.spring.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"/>
    </bean>
    <!--Dao层-->
    <bean id="accountDao" class="com.spring.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--数据源配置-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8"/>
        <property name="username" value="root"/>
        <property name="password" value="w327191248"/>
    </bean>
    <!--Spring中基于XML的声明式事务控制-->
    <!--1.配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--2.配置事务的通知,此时需要导入事务的约束,tx和aop的都需要-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--配置事务的属性-->
        <tx:attributes>
            <tx:method name="*" propagation="REQUIRED" read-only="false"/>
            <tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
        </tx:attributes>
    </tx:advice>
    <!--3.配置AOP-->
    <aop:config>
        <!--配置切入点表达式-->
        <aop:pointcut id="pt1" expression="execution(* com.spring.service.impl.*.*(..))"/>
        <!--4.建立事务通知和切入表达式的对应关系-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/>
    </aop:config>
</beans>com.spring.domain.Account同上
com.spring.dao.impl.AccountDaoImpl
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao{
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Override
    public Account findAccountById(Integer id) {
        List<Account> accounts = jdbcTemplate.query("select *from account where id=?", new BeanPropertyRowMapper<Account>(Account.class), id);
        return accounts.isEmpty() ? null : accounts.get(0);
    }
    @Override
    public Account findAccountByName(String accountName) {
        List<Account> accounts = jdbcTemplate.query("select *from account where name=?", new BeanPropertyRowMapper<Account>(Account.class), accountName);
        if (accounts.isEmpty()) {
            return null;
        }
        if (accounts.size() > 1) {
            throw new RuntimeException("结果集不唯一");
        }
        return accounts.get(0);
    }
    @Override
    public void updateAccount(Account account) {
        jdbcTemplate.update("update account set name=?,money=? where id=?", account.getName(), account.getMoney(), account.getId());
    }
}com.spring.service.impl.AccountServiceImpl
使用@Transactional可以配置在类上或者方法上开启事务,具体参数
@Service("accountService")
@Transactional(propagation = Propagation.SUPPORTS,readOnly = true)
public class AccountServiceImpl implements IAccountService {
    @Autowired
    private IAccountDao accountDao;
    @Override
    public Account findAccountById(Integer id) {
        return accountDao.findAccountById(id);
    }
    @Override
    @Transactional(propagation = Propagation.REQUIRED,readOnly = false)
    public void transfer(String sourceName, String targetName, Float money) {
        //1.根据名称查询转出账户
        Account source = accountDao.findAccountByName(sourceName);
        //2.根据名称查询转入账户
        Account target = accountDao.findAccountByName(targetName);
        //3.转出账户减钱
        source.setMoney(source.getMoney()-money);
        //4.转入账户加钱
        target.setMoney(target.getMoney()+money);
        //5.更新转出账户
        accountDao.updateAccount(source);
        //人为发生异常
        int i = 1/0;
        //5.更新转入账户
        accountDao.updateAccount(target);
    }
}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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
</beans>
配置:
<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
    <!--配置Spring创建容器时要扫描的包-->
    <context:component-scan base-package="com.spring"></context:component-scan>
    <!--配置JdbcTemplate使用-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--数据源配置-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8"/>
        <property name="username" value="root"/>
        <property name="password" value="w327191248"/>
    </bean>
    <!--1.配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--2.开启Spring对注解事务的支持-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>使用代码代替bean.xml
主配置类
com.spring.config.SpringConfiguration
/**
 * 1.声明配置类
 * 2.配置Spring创建容器时要扫描的包
 * 3.声明子配置类
 * 4.声明配置文件
 * 5.开启注解事务支持
 */
@Configuration
@ComponentScan("com.spring")
@Import({JdbcConfig.class,TransactionConfig.class})
@PropertySource(value = "jdbcConfig.properties")
@EnableTransactionManagement
public class SpringConfiguration {
}事务相关配置类
com.spring.config.TransactionConfig
public class TransactionConfig {
    /**
     * 用于创建事务管理器对象
     * @param dataSource
     * @return
     */
    @Bean(name="transactionManager")
    public PlatformTransactionManager createPlatformTransactionManager(DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }
}连接数据库相关配置类
com.spring.config.JdbcConfig
public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;
    /**
     * 创建JdbcTemplate对象
     * @param dataSource
     * @return
     */
    @Bean(name = "jdbcTemplate")
    public JdbcTemplate createJdbcTemplate(DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }
    /**
     * 配置数据源
     * @return
     */
    @Bean(name = "dataSource")
    public DataSource createDataSource(){
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }
}
resources.jdbcConfig.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8
jdbc.username=root
jdbc.password=123456原文:https://www.cnblogs.com/JMWan233/p/12446220.html