概述
Tomcat Javaweb服务器 帮助我们去集中去处理请求和响应 包括完成Servlet以及相关代码的运行和解析
而其中Servlet引擎 最核心的功能
Controller | 控制器 |
---|---|
Service | 业务处理 |
DAO | 数据库的访问和操作 |
DB | 数据库 |
设计模式
-
⼴义概念
⾯向对象设计中,解决特定问题的经典代码
-
狭义概念
GOF4⼈帮定义的23种设计模式:⼯⼚、适配器、装饰器、⻔⾯、代理、模板… GOF4 (面向对象领域的4个大师)
工厂模式
好处:解耦合
对象的创建方式:
1.直接调用构造方法创建对象 UserService userService=new UserServiceImpl();
把接口的实现类硬编码在程序中
这行代码有强关联关系,如果想改变UserServiceImpl
这行代码,那么整个类都要改变
//UserServiceImpl userService = new UserServiceImpl();
//这里只需要更改BeanFactory就可以更改UserServiceImpl
UserService userService = BeanFactory.getUserservice(); // return new UserServiceImpl();
public class BeanFactory {
public static UserService getUserservice(){
return new UserServiceImpl();
}
}
2.通过反射的形式创建对象解耦合
public class BeanFactory {
public static UserService getUserservice() throws Exception {
Class<?> clazz = Class.forName("com.zhang.basic.UserServiceImpl");
return (UserService) clazz.newInstance();
}
}
//配置文件的方式
private static final Properties env = new Properties() ;
static {
InputStream resourceAsStream = BeanFactory.class.getResourceAsStream("/applicationContext.properties");
try {
env.load(resourceAsStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static UserService getUserservice() throws Exception {
Class<?> clazz = Class.forName(env.getProperty("UserService"));
return (UserService) clazz.newInstance();
}
#只需要更改配置文件的值就可以更改对应的UserServiceImpl
UserService = com.zhang.basic.UserServiceImpl
方法声明5个要素 修饰符、返回值类型、方法名、参数表、异常(可选)
通用工厂文件
public class BeanFactory {
private static Properties env = new Properties();
//IO 系统级资源 尽量避免重复打开IO 而且最好是在程序启动的时候一次性读取想要的内容 用静态代码块的方式来完成
static{
try {
//为避免耦合 类名最好写在配置文件中
//第一步 获得IO输入流
InputStream inputStream=BeanFactory.class.getResourceAsStream("/applicationContext.properties");
//第二步 文件内容到封装 Properties集合中 key = userService value = com.baizhixx.UserServiceImpl
env.load(inputStream);
inputStream.close(); //关闭IO输入流
} catch (IOException e) {
e.printStackTrace();
}
}
/* 这里结构所使用的代码基本一致 只有小配置文件的key是有所区别的
* 所以为每一个对象提供一个独立的工厂是没有价值和意义的
*/
public static Object getBean(String key){
Object ret = null;
try {
Class clazz = Class.forName(env.getProperty(key));
ret = clazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return ret;
}
/* test文件中 */
@Test
public void test1() {
UserService userService = (UserService)BeanFactory.getBean("userService");
//返回的是Object但实际需要的是UserService 所以需要强制类型转换
userService.login("name", "suns"); //具体user Service对象的使用
User user = new User("suns", "123456");
userService.register(user);
}
将文件内容封装到集合中
Java Properties类:用于读取java的配置文件
https://www.cnblogs.com/bakari/p/3562244.html
load ( InputStream inStream),从输入流中读取属性列表(键和元素对)。通过对指定的文件(比如说xxx.properties 文件)进行装载来获取该文件中的所有键值对。以供 getProperty ( String key) 来搜索。
第一个Spring程序
环境配置
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.14.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<?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="person" class="com.zhang.basic.Person"/>
</beans>
@org.junit.Test
public void test3(){
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
Person person = (Person) ctx.getBean("person");
}
Spring⼯⼚的相关的⽅法
//第一种方式
Person person = (Person) ctx.getBean("person");
//第二种方式 通过这种⽅式获得对象,就不需要强制类型转换
Person person =ctx.getBean("person",Person.class);
//第三种方式
//当前Spring的配置⽂件中 只能有⼀个bean class是Person类型
Person person =ctx.getBean(Person.class);
//获取的是 Spring工厂配置文件中所有bean标签的id值 person person1
String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println("beanDefinitionName = " + beanDefinitionName);
}
//根据类型获得Spring配置文件中对应的id值
String[] beanNamesForType = ctx.getBeanNamesForType(Person.class);
for (String id : beanNamesForType) {
System.out.println("id = " + id);
}
//用于判断是否存在指定id值得bean,不能判断name值
if (ctx.containsBeanDefinition("person")) {
System.out.println("true = " + true);
}else{
System.out.println("false = " + false);
}
//用于判断是否存在指定id值得bean,也可以判断name值
if (ctx.containsBean("p")) {
System.out.println("true = " + true);
}else{
System.out.println("false = " + false);
}
注入
通过Spring⼯⼚及配置⽂件,为所创建对象的成员变量赋值
为什么需要注⼊?
通过编码的⽅式,为成员变量进⾏赋值,存在耦合
好处:解耦合
如何进⾏注⼊?
- 类的成员变量提供set get⽅法
- 配置spring的配置⽂件
String+8种基本类型
private Integer id;
private String name;
//Getter and Setter
public Integer getId() {return id;}
public void setId(Integer id) {this.id = id;}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
<bean id="person" name="p" class="com.baizhiedu.basic.Person">
<property name="id">
<value>20</value>
</property>
<property name="name">
<value>zy</value>
</property>
</bean>
基于属性简化
<!-- value 属性只能简化8种基本类型 + String 注入标签-->
<bean id="person" name="p" class="com.baizhiedu.basic.Person">
<property name="id" value="20"/>
<property name="name" value="zy"/>
</bean>
基于p命名空间的简化
在
P:
中间按alt+enter 选择Create namespace declaration
<bean id="person" name="p" class="com.baizhiedu.basic.Person" p:id="20" p:name="zy"/>
数组
<property name="addresses">
<list>
<value>zpark</value>
<value>shangdi</value>
<value>xierq</value>
<value>xierq</value>
<value>xierq</value>
</list>
</property>
Set集合
<property name="tels">
<set>
<value>138111111</value>
<value>139111111</value>
<value>166111111</value>
<value>166111111</value>
<value>166111111</value>
</set>
</property>
List集合
<property name="addresses">
<list>
<value>zpark</value>
<value>shangdi</value>
<value>xierq</value>
<value>xierq</value>
<value>xierq</value>
</list>
</property>
Map集合
<!--注意: map -- entry -- key有特定的 标签 <key></key>
值根据对应类型选择对应类型的标签-->
<property name="qqs">
<map>
<entry>
<key><value>suns</value></key>
<value>3434334343</value>
</entry>
<entry>
<key><value>chenyn</value></key>
<value>73737373</value>
</entry>
</map>
</property>
Map<String, String> qqs = person.getQqs();
Set<String> keys = qqs.keySet(); //通过keySet获得对象里的key 然后通过对象.get(key)获得value
for (String key : keys) {
System.out.println(key + " value is " + qqs.get(key));
}
Properites
<!--Properties类型 特殊的Map key=String
value=String-->
<props>
<prop key="key1">
value1
</prop>
<prop key="key2">
value2
</prop>
</props>
为用户自定义类型赋值
public class UserServiceImpl implements UserService {
// private UserDAO userDAO = new UserDAOImpl();
// private UserDAO userDAO = (UserDAO)BeanFactory.getBean("userDAO");
private UserDAO userDAO; //具体的赋值使用配置文件来完成->解耦合
public UserDAO getUserDAO() {
return userDAO;
}
public void setUserDAO(UserDAO userDAO) { //通过set方法为UserDAO对象赋值
this.userDAO = userDAO;
}
@Override
public void register(User user) {
userDAO.save(user);
}
@Override
public void login(String name, String password) {
userDAO.queryUserByNameAndPassword(name, password);
}
}
userService接口里的UserServiceImpl想要使用UserDAO接口里的UserDAOImpl类的方法
<bean id="userService" class="com.baizhiedu.basic.UserServiceImpl">
<property name="userDAO">
<bean class="com.baizhiedu.basic.UserDAOImpl"/>
</property>
</bean>
@Test
public void test10() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
UserService userService = (UserService) ctx.getBean("userService");
userService.register(new User("suns", "123456")); //UserDAO里面的方法可以使用
userService.login("xiaohei", "999999");
}
自定义类型赋值第二种方式
第⼀种赋值⽅式存在的问题:
- 配置⽂件代码冗余;
- 被注入的对象 (UserDAO)多次创建,浪费(JVM)内存资源。
<bean id="userDAO" class="com.baizhiedu.dao.UserDAOImpl"></bean>
<bean id="userService" class="com.baizhiedu.service.UserServiceImpl">
<!--这里的name是Setter里的形参 public void setUserDAO(UserDAO userDAO) {this.userDAO = userDAO; }-->
<!-- ↑ -->
<property name="userDAO">
<ref bean="userDAO"/>
</property>
</bean>
基于属性简化
<bean id="userDAO" class="com.baizhiedu.dao.UserDAOImpl"></bean>
<bean id="userService" class="com.baizhiedu.service.UserServiceImpl">
<property name="userDAO" ref="userDAO"/>
</bean>
基于p命名空间的简化
<bean id="userDAO" class="com.baizhiedu.dao.UserDAOImpl"></bean>
<bean id="userService" class="com.baizhiedu.service.UserServiceImpl" p:userDAO-ref="userDAO"/>
构造注入
public class Customer {
private String name;
private int age;
public Customer(String name) {this.name = name;}
public Customer(int age) {this.age = age;}
public Customer(String name, int age) { //被重载的方法必须改变参数列表(参数个数或类型或顺序不一样);
this.name = name;
this.age = age;
}
}
<bean id="customer" class="com.baizhiedu.basic.constructer.Customer">
<constructor-arg type="int">
<value>20</value>
</constructor-arg>
</bean>
反转控制
反转控制(IOC Inverse of Control)
控制:对于成员变量赋值的控制权;
反转控制:把对于成员变量赋值的控制权,从代码中转移(反转)到 Spring ⼯⼚和配置⽂件中完成。
好处:解耦合;
底层实现:工厂设计模式;
依赖注入
依赖注入 (Dependency Injection - DI)
注⼊:通过 Spring 的⼯⼚及配置⽂件,为对象(bean,组件)的成员变量赋值;
依赖注⼊:当⼀个类需要另⼀个类时,就意味着依赖,⼀旦出现依赖,就可以把另⼀个类作为本类的成员变量,最终通过 Spring 配置⽂件进⾏注⼊(赋值)
好处:解耦合;
FactoryBean 接口
public class ConnectionFactoryBean implements FactoryBean<Connection> {
// 用于书写创建复杂对象时的代码
@Override
public Connection getObject() throws Exception {
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/spring", "root", "1234");
return conn;
}
// 返回创建的复杂对象的类型
@Override
public Class<Connection> getObjectType() {
return Connection.class;
}
// 是否单例
@Override
public boolean isSingleton() {
return false; // 每一次都创建新的复杂对象
// return true; // 只创建一次这种类型的复杂对象
}
}
<!--class 指定了 ConnectionFactoryBean, 获得的是该类创建的复杂对象 Connection -->
<bean id="conn" class="com.baizhiedu.factorybean.ConnectionFactoryBean"/>
如果就想获得
FactoryBean
类型的对象,加个&
,ctx.getBean("&conn")
ConnectionFactoryBean cfb = (ConnectionFactoryBean) ctx.getBean("&conn");
Spring 内部运行流程:
- 配置文件中通过 id conn 获得 ConnectionFactoryBean 类的对象 ,进而通过 instanceof 判断出是 FactoryBean 接⼝的实现类;
- Spring 按照规定 getObject() —> Connection;
- 返回 Connection;
实例工厂
- 避免 Spring 框架的侵⼊;
- 整合遗留系统;
public class ConnectionFactory {
public Connection getConnection() {
Connection conn = null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/spring?useSSL=false", "root", "1234");
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
return conn;
}
}
<!--实例工厂-->
<!-- 先创建出工厂实例 实例方法必须先有对象 再有对应的方法调用 -->
<bean id="connFactory" class="com.baizhiedu.factorybean.ConnectionFactory"/>
<!-- 通过工厂实例里的方法创建复杂对象 -->
<bean id="conn" factory-bean="connFactory" factory-method="getConnection"/>
静态工厂
<!--静态工厂-->
<bean id="conn" class="com.baizhiedu.factorybean.StaticFactoryBean" factory-method="getConnection"/>
控制 Spring 工厂创建对象的次数
控制简单对象的创建次数
配置文件中进行配置:
sigleton:只会创建⼀次简单对象,默认值;
prototype:每⼀次都会创建新的对象;
<!--控制简单对象创建次数-->
<bean id="scope" scope="singleton" class="com.baizhiedu.scope.Scope"/>
控制复杂对象的创建次数
如果是 FactoryBean 方式创建的复杂对象:
public class xxxFactoryBean implements FactoryBean {
public boolean isSingleton() {
return true; // 只会创建⼀次
// return false; // 每⼀次都会创建新的
}
}
对象的生命周期
创建阶段
Spring 工厂何时创建对象?
scope="prototype"
:Spring 工厂在获取对象ctx.getBean("xxx")
的同时,创建对象。scope="singleton"
:Spring 工厂创建的同时,创建对象。
通过配置<bean lazy-init="true"/>
也可以实现工厂获取对象的同时,创建对象。
初始化阶段
什么时候?Spring 工厂在创建完对象后,调用对象的初始化方法,完成对应的初始化操作。
初始化方法提供:程序员根据需求,提供初始化方法,最终完成初始化操作。
初始化方法调用:Spring 工厂进行调用。
提供初始化方法的两种方式:
InitializingBean
接口:
public cass Product implements InitializingBean {
//程序员根据需求实现的方法, 完成初始化操作
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("Product.afterPropertiesSet");
}
}
对象中提供一个普通的初始化方法,配置文件种配置 init-method
:
public class Product {
public void myInit() {
System.out.println("Product.myInit");
}
}
<bean id="product" class="com.baizhiedu.life.Product" init-method="myInit"/>
- 如果⼀个对象既实现
InitializingBean
同时⼜提供的 普通的初始化方法,执行顺序?
先执行InitializingBean
,再执行 普通初始化方法。 - 注入⼀定发⽣在初始化操作的前面。
销毁阶段
Spring 销毁对象前,会调用对象的销毁方法,完成销毁操作。
Spring 什么时候销毁所创建的对象?ctx.close();
销毁方法提供:程序员根据业务需求,定义销毁方法,完成销毁操作
销毁方法调用:Spring 工厂进行调用。
开发流程与初始化操作一样,提供销毁方法的两种方式:
DisposableBean 接口:
public class Product implements DisposableBean {
// 程序员根据⾃⼰的需求, 定义销毁方法, 完成销毁操作
@Override
public void destroy() throws Exception {
System.out.println("Product.destroy");
}
}
对象中提供一个普通的销毁方法,配置文件种配置 destroy-method
:
public class Product {
// 程序员根据⾃⼰的需求, 定义销毁方法, 完成销毁操作
public void myDestory() {
System.out.println("Product.myDestory");
}
}
<bean id="product" class="com.baizhiedu.life.Product" destroy-method="myDestory"/>
- 销毁方法的操作只适用于
scope="singleton"
,初始化操作都适用。 - 销毁操作到底是什么?
资源的释放:io.close()
、connection.close()
、…
配置文件参数化
${key}
jdbc.driverClassName = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/spring?useSSL=false
jdbc.username = root
jdbc.password = 1234
<!--Spring的配置文件与⼩配置文件进行整合 classpath代表类路径:class文件夹这个路径-->
<!--resources 下的文件在整个程序编译完后会被放到 classpath 目录下,src.main.java中的文件也是-->
<context:property-placeholder location="classpath:/db.properties"/>
<!-- 在 Spring 配置文件中通过 ${key} 获取小配置文件中对应的值 -->
<bean id="conn" class="com.baizhiedu.factorybean.ConnectionFactoryBean">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
自定义类型转换器
public class MyDateConverter implements Converter<String, Date> { //<原始类型,转换好的类型>
/*
convert方法作用: String ---> Date
SimpleDateFormat sdf = new SimpleDateFormat();
sdf.parset(String) ---> Date
参数:
source : 代表的是配置文件中, 日期字符串 <value>2020-10-11</value>
return : 当把转换好的 Date 作为 convert 方法的返回值后,
Spring ⾃动的为birthday属性进行注入(赋值)
*/
@Override
public Date convert(String source) { //声明的是Data类型的转换器
Date date = null;
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
date = sdf.parse(source);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
}
<!--创建 MyDateConverter 对象-->
<bean id="myDateConverter" class="com.baizhiedu.converter.MyDateConverter"/>
<!--用于注册类型转换器-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<ref bean="myDateConverter"/>
</set>
</property>
</bean>
<bean id="good" class="com.baizhiedu.converter.Good">
<property name="name" value="zy"/>
<property name="birthday" value="2020-6-12"/>
</bean>
细节处理
MyDateConverter
中的日期的格式,通过 依赖注入 的方式,由配置文件完成赋值。
public class MyDateConverter implements Converter<String, Date> {
private String pattern;
@Override
public Date convert(String source) {
Date date = null;
try {
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
date = sdf.parse(source);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
public String getPattern() {
return pattern;
}
public void setPattern(String pattern) {
this.pattern = pattern;
}
}
<!-- 配置文件完成对日期格式的赋值 -->
<bean id="myDateConverter" class="com.baizhiedu.converter.MyDateConverter">
<property name="pattern" value="yyyy-MM-dd"/>
</bean>
ConversionSeviceFactoryBean
定义 id属性,值必须是 conversionService
;
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<ref bean="myDateConverter"/>
</set>
</property>
</bean>
Spring 框架其实内置了日期类型的转换器:日期格式必须是 2020/06/12
。
<bean id="good" class="com.baizhiedu.converter.Good">
<property name="name" value="zhenyu"/>
<property name="birthday" value="2012/12/12"/>
</bean>
后置处理 Bean
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return null; //Object bean, String beanName 类名 对象名
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Category) { //BeanPostProcessor 会对 Spring 工厂创建的所有对象进行加工
Category category = (Category) bean;
category.setName("baizhiedu");
return category;
}
return bean;
}
}
<bean id="myBeanPostProcessor" class="com.baizhiedu.beanpost.MyBeanPostProcessor"/>
静态代理
静态代理:为每⼀个原始类,手工编写⼀个代理类(.java .class)
接口
public interface UserService {
void register(User user);
boolean login(String name, String password);
}
原始类
public class UserServiceImpl implements UserService {
@Override
public void register(User user) {
System.out.println("UserServiceImpl.register 业务运算 + DAO");
}
@Override
public boolean login(String name, String password) {
System.out.println("UserServiceImpl.login 业务运算 + DAO");
return true;
}
}
代理类
代理类是为原始类添加额外功能的
/**
* 静态代理类编码实现
*/
public class UserServiceProxy implements UserService { // 实现原始类相同的接口
private UserService userService = new UserServiceImpl(); // 代理类中必须有原始类
@Override
public void register(User user) {
System.out.println("---log---"); // 额外功能
userService.register(user);
}
@Override
public boolean login(String name, String password) {
System.out.println("---log---"); // 额外功能
return userService.login(name, password);
}
}
动态代理开发
搭建开发环境
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.14.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
创建原始对象(目标对象)
public interface UserService {
void register(User user);
boolean login(String name, String password);
}
public class UserServiceImpl implements UserService {
@Override
public void register(User user) {
System.out.println("UserServiceImpl.register 业务运算 + DAO");
}
@Override
public boolean login(String name, String password) {
System.out.println("UserServiceImpl.login 业务运算 + DAO");
return true;
}
}
额外功能
MethodBeforeAdvice
接口
public class Before implements MethodBeforeAdvice {
/**
* 作用: 把需要运行在原始方法执行之前运行的额外功能, 书写在 before 方法中
*/
@Override
public void before(Method method, Object[] objects, Object target) throws Throwable {
System.out.println("---method before advice log---");
}
}
<!-- 额外功能 -->
<bean id="before" class="com.baizhiedu.aop.Before"/>
定义 切入点:额外功能的加入
⽬的: 由程序员根据⾃⼰的需要,决定额外功能加入给哪个原始方法(register、login)
<!--所有方法都做为切入点,加入额外的功能(register、login)-->
<aop:config>
<aop:pointcut id="pc" expression="execution(* * (..))"/>
</aop:config>
组装
<aop:config>
<aop:pointcut id="pc" expression="execution(* * (..))"/>
<!--组装:目的把切入点与额外功能进行整合-->
<aop:advisor advice-ref="before" pointcut-ref="pc"/>
</aop:config>
调用
//Spring 的工厂通过原始对象的 id 值获得的是代理对象
//获得代理对象后,可以通过声明接口类型,进行对象的存储
@Test
public void test() {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
UserService userService = (UserService) ctx.getBean("userService");
userService.login("admin", "1234");
userService.register(new User());
}
动态字节码技术:通过第三方动态字节码框架,在 JVM 中创建对应类的字节码,进而创建对象,当虚拟机结束,动态字节码跟着消失。
结论:动态代理不需要定义类文件,都是 JVM 运行过程中动态创建的,所以不会造成类⽂件数量过多,影响项目管理的问题。
动态代理使得 额外功能的维护性大大增强
动态代理开发详解
MethodBeforeAdvice
作用:需要把运行在原始方法执行之前运行的额外功能,书写在before方法中
Method: 额外功能所增加给的那个原始方法
login方法register方法
showOrder方法
Object[]: 额外功能所增加给的那个原始方法的参数。String name,String password
UserObject: 额外功能所增加给的那个原始对象 UserServiceImpl
OrderServiceImpl
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("-----new method before advice log------");
}
MethodInterceptor(方法拦截器)
public class Around implements MethodInterceptor { //比如事务、抛出异常
@Override
public Object invoke(MethodInvocation Invocation) throws Throwable {
System.out.println("---额外功能运行在原始方法执行之前---");
Object ret = Invocation.proceed(); // 原始方法运行, 获取原始方法的返回值
System.out.println("---额外功能运行在原始方法执行之后---");
return ret;
}
}
<!-- 额外功能 -->
<bean id="around" class="com.baizhiedu.dynamic.Around"/>
<aop:config>
<aop:pointcut id="pc" expression="execution(* * (..))"/>
<!--组装:目的把切入点与额外功能进行整合-->
<aop:advisor advice-ref="around" pointcut-ref="pc"/>
</aop:config>
切入点表达式
方法切入点
定义一个方法
public void add(int i, int j)
* * (..)
* * (..) --> 所有方法
* ---> 修饰符 返回值
* ---> 方法名
() ---> 参数表
.. ---> 对于参数没有要求 (参数有没有,参数有⼏个都行,参数是什么类型的都行)
<!-- 定义login作为切入点 -->
<aop:pointcut id="pc" expression="execution(* login (..))"/>
<!-- 定义register作为切入点 -->
<aop:pointcut id="pc" expression="execution(* register (..))"/>
精准方法切入点限定
<aop:pointcut id="pc" expression="execution(* com.baizhiedu.proxy.UserServiceImpl.login(..))"/>
<aop:pointcut id="pc" expression="execution(* com.baizhiedu.proxy.UserServiceImpl.login(String, String))"/>
类切入点
# 类中所有的方法加入了额外功能
<aop:pointcut id="pc" expression="execution(* com.baizhiedu.proxy.UserServiceImpl.*(..))"/>
# 忽略包
1. 类只存在一级包
<aop:pointcut id="pc" expression="execution(* *.UserServiceImpl.*(..))"/>
2. 类存在多级包
<aop:pointcut id="pc" expression="execution(* *..UserServiceImpl.*(..))"/>
包切入点(实战中用的多)
# 切入点包中的所有类,必须在proxy中,不能在proxy包的⼦包中
<aop:pointcut id="pc" expression="execution(* com.baizhiedu.proxy.*.*(..))"/>
# 切入点当前包及其⼦包都生效
<aop:pointcut id="pc" expression="execution(* com.baizhiedu.proxy..*.*(..))"/>
切入点函数(execution、args、within)
args
args
作用:主要用于 函数(方法) 参数的匹配;
切入点:方法参数必须得是 2 个字符串类型的参数
# 使用 execution
<aop:pointcut id="pc" expression="execution(* *(String, String))"/>
# 使用 args
<aop:pointcut id="pc" expression="args(String, String)"/>
within
within
作用:主要用于进行 类、包切入点表达式 的匹配。
切入点: UserServiceImpl 这个类
# 使用 execution
<aop:pointcut id="pc" expression="expression(* *..UserServiceImpl.*(..))"/>
# 使用 within
<aop:pointcut id="pc" expression="within(*..UserServiceImpl)"/>
切入点: com.baizhiedu.proxy 这个包
# 使用 execution
<aop:pointcut id="pc" expression="execution(* com.baizhiedu.proxy..*.*(..)"/>
# 使用 within
<aop:pointcut id="pc" expression="within(com.baizhiedu.proxy..*)"/>
@annotation
作用:为具有特殊注解的 方法 加入额外功能。
@Target(ElementType.METHOD) //注解需要用在方法上面
@Retention(RetentionPolicy.RUNTIME) //注解什么时候起作用
public @interface Log {
}
<aop:pointcut id="pc" expression="@annotation(com.baizhiedu.Log)"/>
切入点函数的逻辑运算(and、or)
and 与操作
案例: 方法名叫 login 同时 参数是 2个字符串
# execution
<aop:pointcut id="pc" expression="execution(* login(String, String))"/>
# execution and args
<aop:pointcut id="pc" expression="execution(* login(..)) and args(String, String))"/>
注意:与操作不能⽤于同种类型的切⼊点函数
以下这个是错误的, 因为不存在同时叫 login 和 register 的方法
<aop:pointcut id="pc" expression="execution(* login(..)) and execution(* register(..))"/>
or 或操作:
案例: 方法名叫 register 或 login 的⽅法作为切⼊点
<aop:pointcut id="pc" expression="execution(* login(..)) or execution(* register(..))"/>
AOP
AOP (Aspect Oriented Programing)
⾯向切⾯编程 = Spring动态代理开发
以切⾯为基本单位的程序开发,通过切⾯间的彼此协同,相互调⽤,完成程序的构建
切⾯ = 切⼊点 + 额外功能
OOP (Object Oritened Programing)
⾯向对象编程 Java
以对象为基本单位的程序开发,通过对象间的彼此协同,相互调⽤,完成程序的构建
POP (Producer Oriented Programing)
⾯向过程(⽅法、函数)编程 C
以过程为基本单位的程序开发,通过过程间的彼此协同,相互调⽤,完成程序的构建
AOP的概念:
本质就是Spring得动态代理开发,通过代理类为原始类增加额外功能。
好处:利于原始类的维护
注意:AOP编程不可能取代OOP,OOP编程有意补充。
JDK 的动态代理
public class TestJDKProxy {
/*
1. 借用类加载器 TestJDKProxy
UserServiceImpl
2. JDK8.x前
final UserService userService = new UserServiceImpl();
*/
public static void main(String[] args) {
//1 创建原始对象
UserService userService = new UserServiceImpl();
//2 JDK创建动态代理
InvocationHandler handler = new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("------proxy log --------");
Object ret = method.invoke(userService, args); //原始方法运行
return ret;
}
};
UserService userServiceProxy = (UserService)Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(),userService.getClass().getInterfaces(),handler);
userServiceProxy.login("suns", "123456");
userServiceProxy.register(new User());
}
}
CGlib
CGlib创建动态代理的原理:⽗⼦继承关系创建代理对象,原始类作为⽗类,代理类作为⼦类,这样既可以保证2者⽅法⼀致,同时在代理类中提供新的实现(额外功能+原始⽅法)
public class UserService {
public void login(String name, String password) {
System.out.println("UserService.login");
}
public void register(User user) {
System.out.println("UserService.register");
}
}
package com.baizhiedu.cglib;
import com.baizhiedu.proxy.User;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class TestCglib {
public static void main(String[] args) {
//1 创建原始对象
UserService userService = new UserService();
/*
2 通过cglib方式创建动态代理对象
Proxy.newProxyInstance(classloader,interface,invocationhandler)
Enhancer.setClassLoader()
Enhancer.setSuperClass()
Enhancer.setCallback(); ---> MethodInterceptor(cglib)
Enhancer.create() ---> 代理
*/
Enhancer enhancer = new Enhancer();
enhancer.setClassLoader(TestCglib.class.getClassLoader());
enhancer.setSuperclass(userService.getClass());
MethodInterceptor interceptor = new MethodInterceptor() {
//等同于 InvocationHandler --- invoke
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("---cglib log----");
Object ret = method.invoke(userService, args);
return ret;
}
};
enhancer.setCallback(interceptor);
UserService userServiceProxy = (UserService) enhancer.create();
userServiceProxy.login("suns", "123345");
userServiceProxy.register(new User());
}
}
Spring 工厂如何加工原始对象
思路分析:主要通过 BeanPostProcessor
将原始对象加工为代理对象
public class ProxyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
/*
Proxy.newProxyInstance();
*/
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("----- new Log-----");
Object ret = method.invoke(bean, args);
return ret;
}
};
return Proxy.newProxyInstance(ProxyBeanPostProcessor.class.getClassLoader(),bean.getClass().getInterfaces(),handler);
}
}
基于注解的 AOP 编程的开发
# 通过切⾯类 定义了 额外功能 @Around
定义了 切⼊点 @Around(“execution(* login(…))”)
@Aspect 切⾯类
/*
1. 额外功能
public class MyArround implements MethodInterceptor{
public Object invoke(MethodInvocation invocation){
Object ret = invocation.proceed();
return ret;
}
}
2. 切入点
<aop:config
<aop:pointcut id="" expression="execution(* login(..))"/>
*/
@Aspect
public class MyAspect {
@Around("execution(* login(..))")
public Object arround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("----aspect log ------");
Object ret =joinPoint.proceed();
return ret;
}
}
<bean id="userService" class="com.baizhiedu.aspect.UserServiceImpl"/>
<!--
切面
1. 额外功能
2. 切入点
3. 组装切面
-->
<bean id="arround" class="com.baizhiedu.aspect.MyAspect"/>
<!--告知Spring基于注解进行AOP编程-->
<aop:aspectj-autoproxy proxy-target-class="true" />
切入点复用
切入点复用:在切面类中定义⼀个函数,上面用 @Pointcut
注解。
通过这种方式定义切入点表达式,后续更加有利于切入点复用。
@Aspect
public class MyAspect {
@Pointcut("execution(* *..UserServiceImpl.*(..))")
public void myPointcut(){}
@Around(value="myPointcut()")
public Object arround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("----aspect log ------");
Object ret = joinPoint.proceed();
return ret;
}
@Around(value="myPointcut()")
public Object arround1(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("----aspect tx ------");
Object ret = joinPoint.proceed();
return ret;
}
}
默认情况 AOP 编程 底层应用 JDK动态代理创建方式。
基于注解的 AOP 开发 中切换为 Cglib:
<aop:aspectj-autoproxy proxy-target-class="true"/>
传统的 AOP 开发 中切换为 Cglib:
<aop:config proxy-target-class="true">
...
</aop:config>
AOP 开发中的一个坑
public class UserServiceImpl implements UserService, ApplicationContextAware {
private ApplicationContext ctx;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.ctx = applicationContext;
}
@Log
@Override
public void register(User user) {
System.out.println("UserServiceImpl.register 业务运算 + DAO ");
//throw new RuntimeException("测试异常");
//调用的是原始对象的login方法 ---> 核心功能
/*
设计目的:代理对象的login方法 ---> 额外功能+核心功能
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext2.xml");
UserService userService = (UserService) ctx.getBean("userService");
userService.login();
Spring工厂重量级资源 一个应用中 应该只创建一个工厂
*/
UserService userService = (UserService) ctx.getBean("userService");
userService.login("suns", "123456");
}
@Override
public boolean login(String name, String password) {
System.out.println("UserServiceImpl.login");
return true;
}
}
Object ret = joinPoint.proceed();
return ret;
}
}
**默认情况 AOP 编程 底层应用 JDK动态代理创建方式**。
基于注解的 AOP 开发 中切换为 Cglib:
```xml
<aop:aspectj-autoproxy proxy-target-class="true"/>
传统的 AOP 开发 中切换为 Cglib:
<aop:config proxy-target-class="true">
...
</aop:config>