JavaEE进阶——Spring学习笔记

文章目录

  • Spring
    • ApplicationContext和BeanFactory区别
      • BeanFactory
      • ApplicationContext
      • ApplicationContext和BeanFactory应用场景
    • ApplicationContext三个实现类
      • ClassPathXmlApplicationContext
      • FileSystemXmlApplicationContext
      • AnnotationConfigApplicationContext
  • Bean
    • 创建Bean的三种方式
      • 使用默认构造函数创建
      • 使用普通工厂中方法创建
      • 使用工厂中静态方法创建对象
    • Bean的作用范围
    • Bean的生命周期
  • 依赖注入DI
    • 构造函数注入
    • Set方法注入
    • 注入复杂类型
  • 常用注解
    • 1. 用于创建对象
    • 2. 用于注入数据
    • 3. 用于改变作用范围
    • 4. 和生命周期相关
    • 5.新注解
  • Junit整合
  • 面向切面编程AOP
    • 动态代理
    • Spring中AOP
    • xml配置AOP
    • 注解配置AOP

Spring

ApplicationContext和BeanFactory区别

BeanFactory

Spring里最底层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能
装载:BeanFactory,在构建核心容器时,创建对象采取的策略是延迟加载的方式,在启动的时候不会去实例化Bean,只是当通过容器获取对象时,才会实例化某个Bean对象。

代码测试:

Resource resource = new ClassPathResource("bean.xml");
BeanFactory factory = new XmlBeanFactory(resource);

ApplicationContext

应用上下文,继承了BeanFactory接口,提供了更多的有用的功能

  1. 国际化(MessageSource)

  2. 访问资源,如URL和文件(ResourceLoader)

  3. 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层

  4. 消息发送、响应机制(ApplicationEventPublisher)

  5. AOP(拦截器)

装载:在构建核心容器时,创建对象采取的策略是立即加载的方式,即在ApplicationContext在启动时,读完配置文件即将所有Bean实例化,也可以给Bean配置lazy-init=true 使Bean延迟实例化

ApplicationContext和BeanFactory应用场景

  1. ApplicationContext适用于单例对象,在加载配置文件时,创建Bean对象;所有Bean在启动时候都加载,系统运行速度快
  2. BeanFactory适用于多例对象,启动时占用资源少,对资源要求高的应用有优势

ApplicationContext三个实现类

基于xml方式
ClassPathXmlApplicationContext
FileSystemXmlApplicationContext
基于注解方式
AnnotationConfigApplicationContext

ClassPathXmlApplicationContext

默认解析类路径下配置文件,要求配置文件必须在类路径下,即src下一级,

默认为类路径下,classpath: 前缀可加可不加
使用绝对路径需要加前缀 file:

使用方法如下:
加载一个文件

ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");

加载两个以上文件时,使用字符串数组方式:

ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml","bean.xml"});

加载两个以上文件时,使用通配符方式:

ApplicationContext context = new ClassPathXmlApplicationContext("classpath:/*.xml");

FileSystemXmlApplicationContext

用于加载磁盘任意路径下的配置文件,但需要获取访问权限

file: 前缀可加可不加,默认为文件绝对路径

使用方法:

ApplicationContext context = new  FileSystemXmlApplicationContext("C:\\Users\\Desktop\\bean.xml");

基于xmlk配置文件方式,加载类路径下配置文件更常用

AnnotationConfigApplicationContext

用于读取注解创建容器

ApplicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);

SpringConfiguration为配置类

@Configuration
@ComponentScan(basePackages = "com")
public class SpringConfiguration {
    
    @Bean(name = "runner")
    public QueryRunner createQueryRunner(DataSource dataSource){
        return new QueryRunner(dataSource);
    }

    /**
     * 创建数据源对象
     * @return
     */
    @Bean(name = "dataSource")
    public DataSource createDataSource(){
        ComboPooledDataSource ds = new ComboPooledDataSource();
        try {
            ds.setDriverClass("com.mysql.jdbc.Driver");
            ds.setJdbcUrl("jdbc:mysql:///:3306/test");
            ds.setUser("root");
            ds.setPassword("");
            return ds;
        } catch (Exception e) {
            throw  new RuntimeException(e);
        }
    }
}

Bean

创建Bean的三种方式

JavaBean是JAVA语言写成的可重用软件组件,是一个java类,通过封装属性和方法成为具有某种功能或者处理某种业务的对象

使用默认构造函数创建

在spring配置文件中,配置id和class属性后,且没有其他属性和标签时,采用是默认构造函数创建bean对象,如果此时类中没有默认构造函数,则对象无法创建。
Bean对象:

public class AccountServiceImpl implements IAccountService {
    public AccountServiceImpl() {

    }
    public void saveAccount() {
        System.out.println("保存一个账户");
    }
}

配置文件:

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="accountService" class="com.service.impl.AccountServiceImpl" />
</beans>

使用普通工厂中方法创建

工厂类

public class InstanceFactory {
    public IAccountService getAccountService(){
        return new AccountServiceImpl();
    }
}

配置文件

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="instanceFactory" class="com.factory.InstanceFactory" />
    <bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService" />
</beans>

使用工厂中静态方法创建对象

工厂类中静态方法

public class StaticFactory {
    public static IAccountService getAccountService(){
        return new AccountServiceImpl();
    }
}

配置文件

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="accountService" class="com.factory.StaticFactory" factory-method="getAccountService" />
</beans>

Bean的作用范围

<!--bean的作用范围
        bean标签scope属性:指定bean作用范围
           取值:singleton:单例(默认)
                 prototype:多例
                 request   作用于web应用的请求范围
                 session   作用于web应用的会话范围
                 global-session  集群环境的会话范围(若非集群,它的范围就是session)-->
    <bean id="accountService" class="com.factory.StaticFactory" scope="prototype" factory-method="getAccountService" />

Bean的生命周期

单例对象:

  1. 容器创建时对象出生
  2. 容器销毁是对象销毁

多例对象:

  1. 使用对象时,spring框架创建对象
  2. 对象在使用中一直存在
  3. 当对象长时间不用且无其他对象引用时,由java垃圾回收期回收

依赖注入DI

依赖注入的维护即称为依赖注入
IOC作用:降低程序间(耦合)依赖关系
依赖关系管理由spring来维护:当再某个类中需要其他类对象时,由spring提供,只需在配置文件中说明即可
依赖注入的数据:基本类型和String、其他Bean类型、复杂类型/集合类型
依赖注入的方式:构造函数、set方法、注解

构造函数注入

使用构造函数的方式,给service中的属性传值
要求:类中需要提供一个对应参数列表的构造函数。

constructor-arg 属性:

  1. index:指定参数在构造函数参数列表的索引位置 从开始
  2. type:指定参数在构造函数中的数据类型
  3. name:指定参数在构造函数中的名称,用这个找给谁赋值(常用)
  4. value:它能赋的值是基本数据类型和 String 类型
  5. ref:它能赋的值是其他 bean 类型,必须得是在配置文件中配置过的 bean

代码如下:

public class AccountServiceImpl implements IAccountService {

    //经常变化的数据,不适用于注入方式
    private String name;
    private Integer age;
    private Date birthday;

    public AccountServiceImpl(String name, Integer age, Date birthday) {
        this.name = name;
        this.age = age;
        this.birthday = birthday;

    }

    public void saveAccount() {
        System.out.println("保存一个账户");
    }
}
<bean id="accountService" class="com.service.impl.AccountServiceImpl">
        <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>

特点:

  1. 获取bean对象时,注入数据是必须的操作,否则对象无法创建
  2. 改变了bean对象的实例化方式,在创建对象时,某些用不到的数据,也必须提供

Set方法注入

就是在类中提供需要注入成员的 set 方法。具体代码如下:
使用标签:property
属性:

  1. name:找的是类中 set 方法后面的部分
  2. ref:给属性赋值是其他 bean 类型的
  3. value:给属性赋值是基本数据类型和 string 类
public class AccountServiceImpl implements IAccountService {
    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;
    }

    @Override
    public void saveAccount() {
        System.out.println(name + "," + age + "," + birthday);
    }
} 
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> 
  <property name="name" value="test"></property> 
  <property name="age" value="21"></property>
  <property name="birthday" ref="now"></property> 
</bean> 
<bean id="now" class="java.util.Date"></bean> 

特点:

  1. 创建对象时,没有明确的限制,可以直接使用默认构造函数
  2. 如果某个成员必须有值,set方法无法保证
  3. 相比构造函数,set方式更常用

注入复杂类型

使用常用的set方式:

public class AccountServiceImpl implements IAccountService {
    private String[] myStrs;
    private List<String> myList;
    private Set<String> mySet;
    private Map<String, String> myMap;
    private Properties myProps;

    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, String> myMap) {
        this.myMap = myMap;
    }

    public void setMyProps(Properties myProps) {
        this.myProps = myProps;
    }
}

用于给List 结构集合注入的结构: array,list,set
用于给Map结构集合注入的标签: map,entry,props,prop
结构相同,标签可以互换

 <bean id="accountService" class="com.service.impl.AccountServiceImpl">
    <!-- 在注入集合数据时,只要结构相同,标签可以互换 --> 
    <!-- 给数组注入数据 -->
    <property name="myStrs">
        <set>
            <value>AAA</value>
            <value>BBB</value>
            <value>CCC</value>
        </set>
    </property>
    <!-- 注入 list 集合数据 -->
    <property name="myList">
        <array>
            <value>AAA</value>
            <value>BBB</value>
            <value>CCC</value>
        </array>
    </property>
    <!-- 注入 set 集合数据 -->
    <property name="mySet">
        <list>
            <value>AAA</value>
            <value>BBB</value>
            <value>CCC</value>
        </list>
    </property>
    <!-- 注入 Map 数据 -->
    <property name="myMap">
        <map>
            <entry key="testA" value="aaa"></entry>
            <entry key="testB">
                <value>bbb</value>
            </entry>
        </map>
    </property>
    <!-- 注入 properties 数据 -->
    <property name="myProps">
        <props>
            <prop key="testA">aaa</prop>
            <prop key="testB">bbb</prop>
        </props>
    </property>
</bean>    

常用注解

1. 用于创建对象

与xml配置文件中的<bean>标签实现的功能一样

@Component:把当前对象存入spring容器中
属性:

  • value:用于指定bean的id值,不配置时,默认是类名,且首字母的小写

@Controller: 表现层
@Service : 业务层
@Repository :持久层
以上注解的作用和属性与Component作用相同,是spring提供明确的三层使用的注解,使三层对象更清晰

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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
	<!--告知spring容器扫描的包-->
    <context:component-scan base-package="com"></context:component-scan>

</beans>

2. 用于注入数据

与xml配置文件中的bean标签中写一个<property>标签作用是一样的

@Autowired:自动按照类型注入,只要容器中有唯一的一个bean对象和要注入的类型匹配,即可注入成功

使用位置:可以是变量,也可以是方法上

注意:使用注解时,set方法不是必须的,使用Autowired时,
如果容器中存在value值与类型相对应时,则将该对象注入,不存在则与类的实现接口比对,若存在则注入,否则报错。
如果IOC容器中有多个value值和数据类型匹配时,则首先按类型圈出来匹配的key值,使用变量名称与bean的id继续匹配查找,若存在某个匹配的则注入成功,若存在两个匹配相同时,则报错

JavaEE进阶——Spring学习笔记_第1张图片
@Qualifier:在按照类型注入的基础上在按照名称注入,在给类成员注入时不能单独使用,在给方法参数注入时可以

属性:

  • value;用于指定bean的id
    给类成员注入时,需要和@Autowired配合使用
@Autowired
@Qualifier("accountDao_1")
private IAccountDao accountDao;

在方法参数中的独立使用

@Bean(name = "runner")
public QueryRunner createQueryRunner(@Qualifier("ds2") DataSource dataSource){
        return new QueryRunner(dataSource);
}
@Bean(name = "ds2")
public DataSource createDataSource(){
        ComboPooledDataSource ds = new ComboPooledDataSource();
        try {
            ds.setDriverClass("com.mysql.jdbc.Driver");
            ds.setJdbcUrl("jdbc:mysql:///:3306/test");
            ds.setUser("root");
            ds.setPassword("");
            return ds;
        } catch (Exception e) {
            throw  new RuntimeException(e);
        }
 }

参数DataSource会先按照类型匹配,若没有匹配到或者形参无法在多个匹配中找到符合名称的bean的id时,则会匹配@Qualifier指定的value值

@Resource:直接按照bean的id注入,可以单独使用
属性:

  • name:用于指定bean的id

以上三种注解都只能注入其他bean类型的数据,而基本类型和String无法使用上述注解实现,此外,集合类型只能通过xml配置来实现

@Value :用于注入基本类型和String类型的数据
属性:value 用于指定数据的值,可以使用Spring中的EL表达式(SpEL)
SpEL写法:${表达式}

3. 用于改变作用范围

与xml配置文件中,bean标签中使用scope属性实现的功能一样

@Scope:用于指定bean的作用范围
属性:value,取值为singleton 、prototype; 默认为单例
在这里插入图片描述

4. 和生命周期相关

与bean标签中使用的init-method和destory-method作用一样

@PreDestroy:用于指定销毁的方法

@PostConstruct:用于指定初始化方法

@PostConstruct
public void init(){
   System.out.println("初始化方法");
}

@PreDestroy
public void destory(){
    System.out.println("销毁方法");
}

5.新注解

@Configuration:指定该类是一个配置类

注:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,可以不写

如下:

ApplicationContext ac = AnnotationConfigApplicationContext(SpringConfiguration.class);

JavaEE进阶——Spring学习笔记_第2张图片
@ComponentScan(basePackages = “com”):用于指定spring在创建容器时要扫描的包
属性:value和basePackages一样,指定扫描包
等同于

@Bean:用于把当前方法的返回值作为bean对象存入spring的IOC容器中
属性:name:指定bean的id,不设置时,默认是当前方法的名称

@Bean(name = "runner")
public QueryRunner createQueryRunner(DataSource dataSource){
        return new QueryRunner(dataSource);
}

    /**
     * 创建数据源对象
     * @return
     */
 @Bean(name = "dataSource")
 public DataSource createDataSource(){
        ComboPooledDataSource ds = new ComboPooledDataSource();
        try {
            ds.setDriverClass("com.mysql.jdbc.Driver");
            ds.setJdbcUrl("jdbc:mysql:///:3306/test");
            ds.setUser("root");
            ds.setPassword("");
            return ds;
        } catch (Exception e) {
            throw  new RuntimeException(e);
        }
 }

注:当使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象,查找方式和@Autowired查找方式一样

@import:用于导入其他配置类
属性 value 用于指定其他配置类的字节码
当使用Import注解后,有Import注解的类就是父配置类,导入的都是子配置类
JavaEE进阶——Spring学习笔记_第3张图片
此时JdbcConfig类中不需要再加@Configuration注解

另外并列关系的配置类方式如下:

ApplicationContext ac = AnnotationConfigApplicationContext(SpringConfiguration.class,JdbcConfig.class);

读取配置文件方式获取数据源
@PropertySource:用于指定properties文件的位置

  • 属性: value 指定文件的名称和路径
  • 关键字: classpath 表示类路径下
@Configuration
@ComponentScan(basePackages = "com")
@Import(JdbcConfig.class)
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfiguration {
    
}
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;

    @Bean(name = "runner")
    public QueryRunner createQueryRunner(DataSource dataSource){
        return new QueryRunner(dataSource);
    }
    ...
}
//jdbcConfig.propertiest文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=root
jdbc.password=

注:若是第三方jar包中的类,使用xml配置更方便,若是自己编写的配置类使用注解方式更方便

Junit整合

junit中单元测试集成了一个main方法,即没有main方法也能执行,该方法会判断当前测试类中哪些方法有@Test注解
junit执行测试方法时,并不知道是否采用spring框架,也不会读取配置文件创建容器等操作,即便是配置@Autowired注解也不起效
解决方法:

  • 导入spring整合junit的jar包
<dependency>
   <groupId>org.springframeworkgroupId>
   <artifactId>spring-testartifactId>
   <version>5.0.2.RELEASEversion>
dependency>
  • 使用junit提供的一个注解把原来方法中的main方法替换为spring提供的 即@Runwith
  • 告知spring,ioc创建是xml配置还是注解方式 @ContextConfiguration()
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class client {
    
    @Autowired
    private IAccountService as = null;

    @Test
    public void test(){
        ...
    }
}

@ContextConfiguration:告知spring,容器创建是注解还是xml配置方式
属性:

  • locations:指定xml文件的位置,加上classpath关键字,表示在类路径下
@ContextConfiguration(location = "classpath:bean.xml")
  • classes:指定注解类所在位置
@ContextConfiguration(classes = SpringConfiguration.class)

注:当使用spring 5.x版本,junit版本必须是4.12以上

面向切面编程AOP

将程序重复的代码抽取出来,在需要执行时,使用动态代理的技术,在不修改源码的基础上对已有方法进行增强

AOP作用:

  • 在程序运行期间不修改源码对方法进行增强
  • 减少重复代码
  • 提高开发效率
  • 维护方便

AOP的实现方式即动态代理技术

动态代理

字节码随用随创建,随用随加载
装饰者模式就是静态代理的一种体现

动态代理的两种方式:

  • 基于接口的动态代理
    Proxy类
    被代理类最少实现一个接口,如果没有则不能使用
    如何代理:使用proxy类的种的newProxyInstance方法
    newProxyInstance参数
    ClassLoader:类加载器,用于加载代理对象字节码的,和被代理对象使用相同的类加载器,固定写法
    Class[]:字节码数组,用于让代理对象和被代理对象有相同方法,固定写法。
    InvocationHandler:用于提供增强的代码,即如何代理,实现一个接口的实现类,谁用谁写。
public static void main(String[] args) {
        Producer producer = new Producer();
        Proxy.newProxyInstance(producer.getClass().getClassLoader(), producer.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * 执行被代理对象的任何方法,都会经过该方法。
                     * 此方法有拦截的功能。
                     *
                     * 参数:      
                     *   proxy:代理对象的引用。不一定每次都用得到      
                     *   method:当前执行的方法对象      
                     *   args:执行方法所需的参数      
                     * 返回值:      
                     *   当前执行方法的返回值      
                     */
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //提供增强代码
                        Object returnValue = null;
                        //1.获取方法执行的参数
                        Float money = (Float) args[0];
                        //2.判断当前方法是不是销售
                        if ("saleProduct".equals(method.getName())){
                            //消费者消费10000,厂家拿走0.8,代理商留下0.2
                            returnValue = method.invoke(producer,money*0.8f);
                        }
                        return returnValue;
                    }
                });
}
  • 基于子类的动态代理
    使用 CGLib 的 Enhancer 类创建代理对象
public static void main(String[] args) {
        final Producer producer = new Producer();
       
        /**
         * 基于子类的动态代理
         *   要求:
         *      被代理对象不能是最终类
         *   用到的类:
         *      Enhancer
         *  用到的方法:
         *     create(Class, Callback)
         *  方法的参数:
         *     Class:被代理对象的字节码
         *     Callback:如何代理,使用该接口的子接口实现类 MethodProxy:当前执行方法的代理对象。
         *   @param args
         */
        Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
            /**
             * 执行该对象的任何方法都会经过该方法
             * @param proxy
             * @param method
             * @param args
             * @param methodProxy
             * @return
             * @throws Throwable
             */
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                //提供增强代码
                Object returnValue = null;
                //1.获取方法执行的参数
                Float money = (Float) args[0];
                //2.判断当前方法是不是销售
                if ("saleProduct".equals(method.getName())){
                    //消费者消费10000,厂家拿走0.8,代理商留下0.2
                    returnValue = method.invoke(producer,money*0.8f);
                }
                return returnValue;
            }
        });
        cglibProducer.saleProduct(10000f);
}

动态代理的应用:

  • 解决中文乱码:通过动态代理的方式,增强request的getParameter方法来解决中文乱码问题
@WebFilter(urlPatterns = "/*") //web3.0之后推荐的方式:注解(不使用web.xml配置文件的方式)
public class GenericEncodingFilterDynamicProxy implements Filter {
 
	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
 
	}
 
	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest req = (HttpServletRequest) request; // 转换为协议相关类型
		HttpServletRequest myReq = (HttpServletRequest) Proxy.newProxyInstance(req.getClass().getClassLoader(),
				req.getClass().getInterfaces(), new InvocationHandler() { // new InvocationHandler: 调用处理程序
 
					@Override
					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
						// 判断方法是否是getParameter,增强getParameter(增强post&&get请求方式),equalsIgnoreCase忽略大小写进行比较
						if ("getParameter".equalsIgnoreCase(method.getName())) {
							// 判断请求方式
							String type = req.getMethod();
							if ("get".equalsIgnoreCase(type)) { // get方式
								// 执行getParameter方法获取值,然后,通过new String(Object.getBytes("ISO-8859-1"),
								// "UTF-8");解决get请求中文乱码
								String value = (String) method.invoke(req, args);
								value = new String(value.getBytes("ISO-8859-1"), "UTF-8");
								return value; // 将处理后的值返回
							} else if ("post".equalsIgnoreCase(type)) {
								req.setCharacterEncoding("UTF-8"); // post方式解决中文乱码问题
								return method.invoke(req, args);
							}
						}
						// 不增强
						return method.invoke(req, args);
					}
				});
		chain.doFilter(myReq, response); // 放行
	}
 
	@Override
	public void destroy() {
 
	}
 
}

Spring中AOP

专业术语:

Joinpoint(连接点): 所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的 连接点。
Pointcut(切入点): 所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。
Advice(通知/增强): 所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。 通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
Introduction(引介): 引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方 法或 Field。
Target(目标对象): 代理的目标对象。
Weaving(织入): 是指把增强应用到目标对象来创建新的代理对象的过程。 spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
Proxy(代理): 一个类被 AOP 织入增强后,就产生一个结果代理类。
Aspect(切面): 是切入点和通知(引介)的结合。

通知类型;
JavaEE进阶——Spring学习笔记_第4张图片

xml配置AOP

配置步骤:

    1、把通知Bean也交给spring来管理
    2、使用aop:config标签表明开始AOP的配置
    3、使用aop:aspect标签表明配置切面
            id属性:是给切面提供一个唯一标识
            ref属性:是指定通知类bean的Id。
    4、在aop:aspect标签的内部使用对应标签来配置通知的类型
           我们现在示例是让printLog方法在切入点方法执行之前之前:所以是前置通知
           aop:before:表示配置前置通知
                method属性:用于指定Logger类中哪个方法是前置通知
                pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强

        切入点表达式的写法:
            关键字:execution(表达式)
            表达式:
                访问修饰符  返回值  包名.包名.包名...类名.方法名(参数列表)
            标准的表达式写法:
                public void com.service.impl.AccountServiceImpl.saveAccount()
            访问修饰符可以省略
                void com.service.impl.AccountServiceImpl.saveAccount()
            返回值可以使用通配符,表示任意返回值
                * com.service.impl.AccountServiceImpl.saveAccount()
            包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*.
                * *.*.*.*.AccountServiceImpl.saveAccount())
            包名可以使用..表示当前包及其子包
                * *..AccountServiceImpl.saveAccount()
            类名和方法名都可以使用*来实现通配
                * *..*.*()
            参数列表:
                可以直接写数据类型:
                    基本类型直接写名称           int
                    引用类型写包名.类名的方式   java.lang.String
                可以使用通配符表示任意类型,但是必须有参数
                可以使用..表示有无参数均可,有参数可以是任意类型
            全通配写法:
                * *..*.*(..)

            实际开发中切入点表达式的通常写法:
                切到业务层实现类下的所有方法
                    * com.service.impl.*.*(..)
<!-- 配置Logger类 -->
    <bean id="logger" class="com.utils.Logger"></bean>
    <!--配置AOP-->
    <aop:config>
    	<!-- 配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
              此标签写在aop:aspect标签内部只能当前切面使用。
              它还可以写在aop:aspect外面,此时就变成了所有切面可用
          -->
        <aop:pointcut id="pt1" expression="execution(* com.service.impl.*.*(..))"></aop:pointcut>
        <!--配置切面 -->
        <aop:aspect id="logAdvice" ref="logger">
			
            <!-- 配置前置通知:在切入点方法执行之前执行
            <aop:before method="beforePrintLog" pointcut-ref="pt1" ></aop:before>-->

            <!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个
            <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>-->

            <!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个
            <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>-->

            <!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行
            <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>-->
        </aop:aspect>
    </aop:config>

环绕通知配置:

<!--配置AOP-->
    <aop:config>
        <!-- 配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
              此标签写在aop:aspect标签内部只能当前切面使用。
              它还可以写在aop:aspect外面,此时就变成了所有切面可用
          -->
        <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
        <!--配置切面 -->
        <aop:aspect id="logAdvice" ref="logger">

            <!-- 配置环绕通知 详细的注释请看Logger类中-->
            <aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around>
        </aop:aspect>
</aop:config>
public class Logger {
    /**
     * 环绕通知
     * 问题:
     *      当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
     * 分析:
     *      通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。
     * 解决:
     *      Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
     *      该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
     *
     * spring中的环绕通知:
     *      它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
     */
    public Object aroundPringLog(ProceedingJoinPoint pjp){
        Object rtValue = null;
        try{
            Object[] args = pjp.getArgs();//得到方法执行所需的参数

            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");

            rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)

            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");

            return rtValue;
        }catch (Throwable t){
            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
            throw new RuntimeException(t);
        }finally {
            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
        }
    }
}

注解配置AOP

导入maven坐标:

<dependencies>
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-contextartifactId>
            <version>5.0.2.RELEASEversion>
        dependency>

        <dependency>
            <groupId>org.aspectjgroupId>
            <artifactId>aspectjweaverartifactId>
            <version>1.8.7version>
        dependency>
dependencies>

配置文件:

<?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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 配置spring创建容器时要扫描的包-->
    <context:component-scan base-package="com"></context:component-scan>

    <!-- 配置spring开启注解AOP的支持 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

配置切面类:

@Component("logger")
@Aspect//表示当前类是一个切面类
public class Logger {

    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    private void pt1(){}

    /**
     * 前置通知
     */
//    @Before("pt1()")
    public  void beforePrintLog(){
        System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。");
    }

    /**
     * 后置通知
     */
//    @AfterReturning("pt1()")
    public  void afterReturningPrintLog(){
        System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。");
    }
    /**
     * 异常通知
     */
//    @AfterThrowing("pt1()")
    public  void afterThrowingPrintLog(){
        System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
    }

    /**
     * 最终通知
     */
//    @After("pt1()")
    public  void afterPrintLog(){
        System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。");
    }

    /**
     * 环绕通知
     * 问题:
     *      当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
     * 分析:
     *      通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。
     * 解决:
     *      Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
     *      该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
     *
     * spring中的环绕通知:
     *      它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
     */
    @Around("pt1()")
    public Object aroundPringLog(ProceedingJoinPoint pjp){
        Object rtValue = null;
        try{
            Object[] args = pjp.getArgs();//得到方法执行所需的参数

            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");

            rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)

            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");

            return rtValue;
        }catch (Throwable t){
            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
            throw new RuntimeException(t);
        }finally {
            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
        }
    }
}

注:spring基于注解配置的AOP,四个通知的执行顺序存在问题,环绕通知执行顺序正常

完全使用注解的方式配置方式如下:

@Configuration 
@ComponentScan(basePackages="com") 
@EnableAspectJAutoProxy 
public class SpringConfiguration {

}

你可能感兴趣的:(JavaEE,java,spring)